文章目录
- [1. Web 服务器的作用](#1. Web 服务器的作用)
-
- [1.1 静态网页和动态网页](#1.1 静态网页和动态网页)
- [1.2 CGI (Common Gateway Interface, 公共网关接口)](#1.2 CGI (Common Gateway Interface, 公共网关接口))
- [1.3 网页服务器可以使用的编程语言](#1.3 网页服务器可以使用的编程语言)
- [1.4 服务器端网页(Server page)](#1.4 服务器端网页(Server page))
-
- [1.4.1 ASP(Active Server Pages)](#1.4.1 ASP(Active Server Pages))
- [1.4.2 JSP(Java Server Pages)](#1.4.2 JSP(Java Server Pages))
- [1.4.3 Tomcat](#1.4.3 Tomcat)
- [1.4.4 编程语言的使用情况对比](#1.4.4 编程语言的使用情况对比)
- [2. PHP](#2. PHP)
-
- [2.1 超文本预处理器 (Hypertext Preprocessor)](#2.1 超文本预处理器 (Hypertext Preprocessor))
- [2.2 PHP 的架构](#2.2 PHP 的架构)
-
- [2.2.1 SAPI(Server Application Programming Interface,服务器应用编程接口)](#2.2.1 SAPI(Server Application Programming Interface,服务器应用编程接口))
-
- [2.2.1.1 PHP CLI(Command Line Interface)](#2.2.1.1 PHP CLI(Command Line Interface))
- [2.2.1.2 PHPCGI(Common Gateway Interface)](#2.2.1.2 PHPCGI(Common Gateway Interface))
-
- [2.2.1.2.1 PHP 的 CGI 生命周期](#2.2.1.2.1 PHP 的 CGI 生命周期)
- [2.2.1.3 CGI 和 FastCGI 在 PHP 中的作用和区别](#2.2.1.3 CGI 和 FastCGI 在 PHP 中的作用和区别)
- [2.2.1.4 PHP-FPM(FastCGI Process Manager)](#2.2.1.4 PHP-FPM(FastCGI Process Manager))
- [2.3 PHP 的编程范式(PHP paradigm)](#2.3 PHP 的编程范式(PHP paradigm))
- [2.4 PHP 的基础语法](#2.4 PHP 的基础语法)
-
- [2.4.1 语言结构(Language construct)](#2.4.1 语言结构(Language construct))
- [2.4.2 PHP 关键字(PHP Keywords)](#2.4.2 PHP 关键字(PHP Keywords))
-
- [2.4.2.1 echo](#2.4.2.1 echo)
- [2.4.3 PHP 扩展(PHP extensions)](#2.4.3 PHP 扩展(PHP extensions))
-
- [2.4.3.1 如何配置](#2.4.3.1 如何配置)
- [2.5 Zend 引擎 (Zend Engine)](#2.5 Zend 引擎 (Zend Engine))
-
- [2.5.1 Zend Extensions(Zend 扩展)](#2.5.1 Zend Extensions(Zend 扩展))
- [2.5.2 Just-In-Time (JIT) 编译](#2.5.2 Just-In-Time (JIT) 编译)
- [3. SQL](#3. SQL)
-
- [3.1 数据库](#3.1 数据库)
-
- [3.1.1 数据库的基础概念](#3.1.1 数据库的基础概念)
- [3.1.2 数据库和电子表格(Spreadsheet)](#3.1.2 数据库和电子表格(Spreadsheet))
- [3.1.3 事务(Transaction)和 ACID 原则](#3.1.3 事务(Transaction)和 ACID 原则)
-
- [3.1.3.1 事务(Transaction)](#3.1.3.1 事务(Transaction))
- [3.1.3.2 ACID 原则](#3.1.3.2 ACID 原则)
- [3.1.4 ER 图(Entity-Relationship Diagram)](#3.1.4 ER 图(Entity-Relationship Diagram))
- [3.1.5 主键(Primary Key)和外键(Foreign Key)](#3.1.5 主键(Primary Key)和外键(Foreign Key))
- [3.1.6 数据库设计](#3.1.6 数据库设计)
- 3.1.7数据库中的索引(Index)
- [3.2 SQL(Structured Query Language,结构化查询语言)](#3.2 SQL(Structured Query Language,结构化查询语言))
-
- [3.2.1 SQL 命令的分类](#3.2.1 SQL 命令的分类)
-
- [3.2.1.1 DDL](#3.2.1.1 DDL)
- [3.2.1.2 DML](#3.2.1.2 DML)
-
- [3.2.1.2.1 WHERE](#3.2.1.2.1 WHERE)
- [3.2.1.3 DCL](#3.2.1.3 DCL)
- [3.2.1.4 TCL](#3.2.1.4 TCL)
- [3.2.2 数据库的类型(Database types)](#3.2.2 数据库的类型(Database types))
-
- [3.2.2.1 SQL Databases](#3.2.2.1 SQL Databases)
- [3.2.2.2 NoSQL 数据库](#3.2.2.2 NoSQL 数据库)
- [3.2.2.3 主流的数据库管理系统(DBMS,Database Management System)](#3.2.2.3 主流的数据库管理系统(DBMS,Database Management System))
- [3.3 连接数据库](#3.3 连接数据库)
-
- [3.3.1 连接 MySQL](#3.3.1 连接 MySQL)
- [3.3.2 数据库管理和开发工具](#3.3.2 数据库管理和开发工具)
- [3.4 软件架构模式](#3.4 软件架构模式)
- [3.5 使用 PHPMyAdmin 创建一个数据库表(Table)](#3.5 使用 PHPMyAdmin 创建一个数据库表(Table))
- [3.6 使用 PHP 编程语言来操作数据库](#3.6 使用 PHP 编程语言来操作数据库)
-
- [3.6.1 使用 PDO 操作数据库](#3.6.1 使用 PDO 操作数据库)
- [3.6.2 使用 mysqli 操作数据库](#3.6.2 使用 mysqli 操作数据库)
- [4. 总结](#4. 总结)
-
- [4.1 HTTP 协议](#4.1 HTTP 协议)
- [4.2 PHP 处理 HTML 表单(Form)提交的数据](#4.2 PHP 处理 HTML 表单(Form)提交的数据)
-
- [4.2.1 Cookie 和 Session](#4.2.1 Cookie 和 Session)
-
- [4.2.1.1 使用 PHP 代码在浏览器中设置和读取 Cookie](#4.2.1.1 使用 PHP 代码在浏览器中设置和读取 Cookie)
- [4.3 使用 PHP Session 开发一个完整的登录与登出系统](#4.3 使用 PHP Session 开发一个完整的登录与登出系统)
- [4.3.1 SQL 注入(SQL Injection)](#4.3.1 SQL 注入(SQL Injection))
1. Web 服务器的作用
我们先对我们之前的知识进行一个小小的回顾,从而帮助更好开展我们今天的知识。
Web 服务器是一种软件,它能够理解 URL(网页地址)。
当用户请求某个网页时,Web 服务器会找到服务器上存储的对应文件,并通过 HTTP 协议发送回客户端(用户的浏览器)。

用户在浏览器输入网页地址,发出 HTML File Request(网页请求)。
请求到达 Webserver(Web 服务器)。
Web 服务器查找 File Server(文件存储),找到对应的网页文件。
Web 服务器将文件通过 HTTP 响应发送给用户,用户浏览器显示网页。
所以Web 服务器是用户和存储网页文件之间的中介。
HTTP 是传输协议,用于客户端和服务器之间传递网页数据。
如果用一个比喻,那就是Web 服务器就像图书馆里的图书管理员,你告诉它你想看哪本书(网页),它帮你从书架(文件服务器)拿出来交给你(浏览器)。
1.1 静态网页和动态网页
像电商网站(Amazon)那样,每个产品都可能对应一个单独的 HTML 页面。
如果网站产品非常多,每次更新内容都要手动修改 HTML 页面,这非常麻烦。

动态网页通过在服务器端读取不同的数据,动态生成网页内容。
就像商品货架,货架是网页框架(HTML模板),商品是数据(产品信息)。
优点:
- 不用手动修改每个 HTML 页面。
- 用户访问时,服务器根据请求生成页面内容。
- 更适合产品种类多、数据频繁更新的电商网站。
1.2 CGI (Common Gateway Interface, 公共网关接口)
CGI 是一个标准接口,允许 Web 服务器通过执行程序来处理用户的 HTTPS 请求。
它本身不是程序,而是一种规范(类似 HTTP)。
工作原理:
用户在浏览器中发送请求(HTTP Request)。
服务器接收请求后,通过 CGI 调用程序来处理请求。
程序可以访问 数据库 或 本地文件 来生成动态内容。
处理完成后,服务器将响应返回给浏览器(HTTP Response)。
CGI 程序可以使用多种语言编写,例如:C/C++、Perl、Java 等。
程序运行在服务器上,并生成客户端需要的动态内容。

图中箭头展示了数据流动方向:用户(Web Browser) → HTTP 请求 → Web Server → CGI 程序 → 数据库/文件 → 生成响应 → 浏览器显示。
因此 CGI 是连接用户请求和服务器处理的桥梁,使得网页不仅可以展示静态内容,还能动态生成内容。
1.3 网页服务器可以使用的编程语言

上面列出的编程语言大多数都可以用于开发网页服务器。包括:
Java、C#、PHP、C++、Python、Perl、Ruby、Go、JavaScript、Objective-C、C、Visual Basic 等。
选择哪一种取决于需求、习惯、项目类型和开发者熟悉度。
1.4 服务器端网页(Server page)
ASP(Active Server Page)
- 出现时间:1996年,由微软开发。
- 功能:使用 VBScript 或 JavaScript 生成动态 HTML 内容。
- 扩展能力:可以调用 COM 组件(组件对象模型)来增强功能。
- ASP.NET:ASP 的升级版,支持 .NET 语言,如 C# 和 J#。
JSP(Java Server Page)
- 出现时间:1998年,由 Sun 开发。
- 功能:在单个 .jsp 文件中同时写 HTML + Java 代码。
- 注意:是 Java 代码,不是 JavaScript。
PHP
- 出现时间:1994年,由 Rasmus Lerdorf 开发。
- 功能:最初是 Personal Home Page Tools,是一个 CGI 程序,类似 Perl 和 shell 脚本的方式。
- 用途:用来生成动态网页内容。
1.4.1 ASP(Active Server Pages)
ASP 是微软在 1996 年开发的一种技术,用来在网页中生成动态内容。也就是说,它可以让网页上的内容根据数据库或用户操作实时变化,而不是静态固定的 HTML 页面。
ASP 文件里可以嵌入 VBScript 或 JavaScript 代码。
当用户通过浏览器访问网页时,Web 服务器 会运行这些脚本,处理数据库数据或其他逻辑,然后生成实时的 HTML 页面发送回浏览器。
服务器端的代码和前端显示的 HTML 是动态结合生成的。
下面给出一个示例。
html
<html>
<body>
<%
response.write("Hello World!")
%>
</body>
</html>
<% %> 中的内容是 ASP 脚本。
response.write("Hello World!") 表示生成 HTML 输出内容 "Hello World!"。
优点(Benefit):不需要写大量 HTML 输出语句(print)就能生成页面。
缺点(Drawback):后台代码(VBScript/JS)和前端 HTML 混在一起,可能导致代码难维护。
1.4.2 JSP(Java Server Pages)
JSP 相比传统 Java 程序员来说更友好,特别是在网页开发中。
下面给出一个示例。
html
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<html>
<head>
<meta charset="ISO-8859-1">
<title> Scripting Element Demo </title>
</head>
<body>
<%! int a = 10; %>
<% out.println(" The value of a is: " + a ); %>
</body>
</html>
<% %>:在 HTML 中嵌入 Java 代码。
<%! int a = 10; %>:声明 Java 变量。
<% out.println(...) %>:向网页输出内容。
JSP 会自动把这些嵌入的 Java 代码转换成 Servlet(Java 程序)去执行。

Client(客户端) 发出请求。
Server(服务器) 接收请求。
JSP Processing:服务器把 JSP 文件转换成 Java Servlet 文件(如 helloServlet.java)。
编译生成 .class 文件(如 helloServlet.class)。
执行 .class 文件,生成 HTML 动态页面。
Response:服务器返回 HTML 页面给客户端浏览器。
1.4.3 Tomcat
Apache Tomcat 是一个基于 Java 的开源 Web 应用服务器(server)。
它是一个 Servlet 容器,可以运行 Servlet 和 JSP(Java Server Pages) 的 Web 应用程序。

功能:
- 可以处理客户端浏览器的请求(HTTP/HTTPS)。
- 执行 Servlet 和 JSP 生成动态网页。
- 可以和 Apache HTTP Server 一起工作,作为 Web 服务的一部分。
特点:
- 软件(software):Tomcat 本身是一个软件程序,而不是硬件。
- 开放源代码:任何人都可以下载和使用它来运行 Java Web 应用。
1.4.4 编程语言的使用情况对比

上图是开发者在 Stack Overflow 平台上最常使用的编程语言。
PHP 的使用率在榜单中排名较低(22.54%),说明开发者日常工作中不是最热门的语言。

上图显示了全球网站使用各种服务器端编程语言的百分比。
PHP 占据 78.9%,远高于其他语言,说明很多网站仍然在使用 PHP 作为后端语言。
这一点或许令人好奇,为什么 PHP 不在开发者流行,但是被如此多的网站使用呢?
PHP 在开发者日常使用中不热门,但由于历史遗留、兼容性和广泛的现有网站代码库,它在实际网站部署中仍然非常常见。
2. PHP
2.1 超文本预处理器 (Hypertext Preprocessor)
1997 年,Andi Gutmans 和 Zeev Suraski 对 Rasmus Lerdorf 的 PHP-FI 进行重写,发布了 PHP 3。
1998 年,他们重新设计了 PHP 语法解析器,并推出 Zend 引擎。PHP 4 就是基于 Zend 引擎的第一个产品,并取得了巨大成功。
1999 年,他们共同创立了 Zend 公司。
PHP 脚本嵌入在 HTML 代码中,用 <?php ... ?> 标签包裹。
当客户端(浏览器)请求 PHP 页面时,Web 服务器(如 Apache)会:
- 解释 PHP 脚本;
- 用 PHP 输出替换脚本;
- 将生成的动态 HTML 返回给客户端。
流程如下图所示。

PHP 脚本 → 编译成 Zend Opcode → 进行优化 → 缓存 → 生成 HTML → 压缩 → 浏览器显示。
PHP 是在服务器端执行的,用户看到的是动态生成的 HTML 页面。
2.2 PHP 的架构
PHP 允许把代码直接写在 HTML 文件里。
PHP 标签:<?php ... ?>
浏览器请求 PHP 页面时,Web 服务器(如 Apache 或 Nginx)会执行 PHP 代码,然后把生成的动态 HTML 返回给浏览器。
PHP 的架构如下图所示。

从上到下:
- Application:Web 应用(Apache、thttpd、命令行等)。
SAPI(Server API):服务器接口,PHP 和 Web 服务器之间的桥梁。 - PHP API:提供流处理、输出等功能。
- PHP 核心:PHP 本身的语言解释和执行部分。
- Extensions:扩展库,比如 MySQL、标准库等。
- Zend API & Zend Engine:Zend 引擎是 PHP 的核心执行引擎,Zend API 提供高级功能扩展。
PHP 主要为 Web 开发优化,虽然可以用于一般用途,但大部分人只用在 Web。
PHP 免费、广泛使用、仍在积极维护。
Web 服务器(Nginx、Apache)通过 SAPI 支持 PHP。
2.2.1 SAPI(Server Application Programming Interface,服务器应用编程接口)
正如我们前面所说,SAPI 是 PHP 和 Web 服务器之间的桥梁,让 PHP 能够在不同环境下运行。
PHP 自带多个 SAPI 模块,这些模块可以让 PHP 在不同的环境下运行,包括:
- CLI - Command Line Interface(命令行接口)
允许在命令行下运行 PHP。 - phpdbg - interactive PHP debugger(交互式调试器)
内置交互式调试工具,帮助开发者调试 PHP 程序。 - CGI - Common Gateway Interface(通用网关接口)
允许 Web 服务器执行 PHP 脚本并返回动态内容。 - Embed
将 PHP 嵌入到其他应用程序中。 - FPM
FastCGI Process Manager,用于处理高并发请求。 - Litespeed / Apache2Handler
针对特定 Web 服务器的 PHP 接口,用于集成 PHP 解析。
2.2.1.1 PHP CLI(Command Line Interface)
PHP CLI 是指我们可以在命令行(例如 Windows 的 CMD 或 Linux 的终端)直接运行 PHP 代码。
如下图所示。

PHP CLI 就是让 PHP 脱离网页服务器环境,也能像普通编程语言一样直接运行代码的方式。
2.2.1.2 PHPCGI(Common Gateway Interface)
用户通过浏览器向 Web 服务器发送 PHP 文件请求。
Web 服务器将请求交给 PHP 引擎(服务器端处理)。
PHP 引擎处理 PHP 脚本后,将生成的内容返回给服务器。
服务器再把处理后的 HTML/CSS/JS 等网页内容返回给用户浏览器。
PHP CGI 会利用一些环境变量与请求交互:
- CONTENT_LENGTH:URL 中编码的数据长度(以字节为单位)。
- REMOTE_HOST:客户端的主机名(请求来源)。
- CONTENT_TYPE:数据类型,例如 application/x-www-form-urlencoded。
- QUERY_STRING:URL 中 ? 后面的参数信息。
- REMOTE_ADDR:客户端的 IP 地址。
- REQUEST_METHOD:HTTP 请求方法,例如 GET 或 POST。
- SCRIPT_NAME:正在运行的 CGI 脚本名称(PHP 文件路径/文件名)。
2.2.1.2.1 PHP 的 CGI 生命周期
上图展示了一个 PHP 的 CGI 生命周期。
Client(客户端):用户的浏览器发送请求。
Server(服务器):Web 服务器(如 Apache 或 Nginx)接收到请求。
PHP start:服务器调用 PHP 来处理请求。
PHP 执行任务:PHP 脚本运行,生成动态内容。
PHP died:PHP 执行完成后结束进程,服务器把结果返回给客户端。
对每个 HTTP 请求,PHP 都会启动执行脚本,处理请求(如访问数据库、生成 HTML)。
执行完毕后,PHP 进程就结束(不会一直占用服务器资源)。
2.2.1.3 CGI 和 FastCGI 在 PHP 中的作用和区别
FastCGI 是 CGI 的一个改进版本。
FastCGI 会预先启动一些 PHP 进程,并让这些进程一直运行,处理多个请求。
请求到 Web 服务器 → Web 服务器把请求交给 FastCGI → FastCGI 的进程处理请求 → 返回结果给 Web 服务器 → 再返回给浏览器。
这么做的优点在于:
- 避免每次请求都启动 PHP 进程,提高性能。
- 减少系统资源开销。
2.2.1.4 PHP-FPM(FastCGI Process Manager)
- Web 浏览器请求 PHP 页面:用户在浏览器中访问某个网页(比如一个 PHP 文件)。
- Web 服务器接收请求:Web 服务器(比如 Apache 或 Nginx)收到请求,它本身不直接执行 PHP 代码。
- FastCGI / PHP-FPM 处理请求:
PHP-FPM 会维护一个 工作进程池(Process Pool)。
有一个 监督进程(supervisor process) 来管理这些工作进程。
当有请求到来时,监督进程会选择一个空闲的工作进程来执行 PHP 代码。 - 执行代码:
被分配的工作进程执行 PHP 代码。
执行完毕后,结果返回给 Web 服务器。
Web 服务器再把结果返回给用户的浏览器。 - 工作进程复用:
执行完一个请求后,工作进程不会消失,而是返回到池中,等待下一个请求。
工作进程数量可以动态调整,根据服务器负载来增加或减少进程数。
2.3 PHP 的编程范式(PHP paradigm)

PHP 支持两种编程范式:
- 面向过程编程(Procedural Oriented Programming, POP)
程序按顺序执行。
数据和函数是分开的,函数处理全局数据。
流程通常从"主程序(Main Program)"开始,依次调用各个函数。
数据是全局的,函数操作这些数据。 - 面向对象编程(Object Oriented Programming, OOP)
数据和操作这些数据的方法封装在对象中。
对象包含数据(Data)和方法(Function)。
对象之间可以交互,通过调用方法来操作数据。
强调模块化、封装、复用和可扩展性。
因此 PHP 的面向对象语法示例如下。
interface:接口定义方法规范。
abstract class:抽象类,可以包含属性和方法,不能直接实例化。
class XYZ extends DEF implements iABC:类继承抽象类并实现接口。
public、protected:访问修饰符,控制属性或方法的可访问性。
$this->name:引用当前对象的属性
2.4 PHP 的基础语法
首先 PHP 脚本可以放在文档中的任何位置。
PHP 代码以 <?php 开始,以 ?> 结束。
例如:
php
<?php
// 这里写 PHP 代码
?>
使用 echo 关键字来输出内容。
echo 是 PHP 的语言结构(language construct),可以直接输出文本或变量。
例如:
php
<?php
echo "Hello World!";
?>
上面的代码会在网页上显示 "Hello World!"。
2.4.1 语言结构(Language construct)
在编程中,语言结构(language constructs)和内置函数(built-in functions)有时容易混淆,因为它们看起来行为相似。
区别在于 PHP 解释器处理它们的方式不同。
每种编程语言都有一套词法(tokens)和结构(structures),由语言的解析器(parser)识别。
这些词法和结构就是 language construct。
它们是语言自带的关键字(keywords),是语言语法的一部分。
当 PHP 解析一个文件时,解析器会识别这些 language constructs 并知道如何处理它们,而不需要像普通函数那样去调用或检查。
也就是说,它们是构建 PHP 语法的基础元素。
echo、print、isset、empty 等在 PHP 中都是 language constructs,不是普通函数。
它们可以直接使用,不需要像调用函数那样带括号(尽管某些情况可以加括号)。
2.4.2 PHP 关键字(PHP Keywords)
这里的表格列出了 PHP 的关键字。

大多数关键字的功能和它们的名字差不多,例如 if 是条件判断,for 是循环,echo 是输出。
PHP 的关键字也和其他编程语言的关键字类似,比如 Java、C、Python。
这里就不详细介绍每一项了。
2.4.2.1 echo
正如前文所述,echo 是 PHP 用来输出内容的语言结构(类似打印)。单引号输出原样字符串,双引号可以直接解析变量。
php
$txt1 = "Learn PHP";
$txt2 = "W3Schools.com";
echo '<h2>' . $txt1 . '</h2>';
echo '<p>Study PHP at ' . $txt2 . '</p>';
使用单引号 '。
需要使用 . 拼接字符串和变量。
结果如下。
html
<h2>Learn PHP</h2>
<p>Study PHP at W3Schools.com</p>
```php
echo "<h2>$txt1</h2>";
echo "<p>Study PHP at $txt2</p>";
使用双引号 "。
可以直接在字符串里写变量,不需要拼接。
输出结果一样。
html
<h2>Learn PHP</h2>
<p>Study PHP at W3Schools.com</p>
所有关键词不区分大小写,包括 echo。
比如 ECHO "Hello"; 也可以。
再强调一遍:
单引号 ':原样输出字符串,变量不会被解析。
双引号 ":会解析字符串里的变量。
两者都可以使用,看个人习惯和需求。
2.4.3 PHP 扩展(PHP extensions)
PHP 本身自带了一些基础的内部函数,这就是所谓的 PHP core(PHP 核心)。
这些函数提供了 PHP 的基础功能,比如字符串操作、数组操作等。
很多功能不是 PHP 核心自带的,而是通过 外部组件/扩展 提供的,这些就是 PHP 的扩展(extensions)。
例如 PDO(PHP Data Objects) 扩展,它允许 PHP 与大多数关系型数据库(MySQL、SQLite、PostgreSQL 等)以及部分 NoSQL 数据库进行交互。
如下图所示。

可以通过勾选启用或禁用来控制扩展项。
比如 mysqli、pdo_mysql 是用来连接 MySQL 数据库的扩展。
json 用于处理 JSON 数据。
xmlreader、xmlwriter 用于处理 XML。
还有像 mbstring 用于多字节字符串处理。
2.4.3.1 如何配置
每个 PHP 扩展都是一个可执行程序。
- 在 Windows 系统上是 .dll 文件。
- 在 Linux 或 macOS 上是 .so 文件。
- 这些扩展文件提供了 PHP 本身没有的功能,例如连接数据库、处理 XML、压缩数据等。
PHP 需要在 php.ini 文件中声明要使用的扩展。
在 php.ini 文件里使用 extension=扩展名 来加载扩展。
如下图所示。

extension=php_pdo_sqlsrv_73_nts_x64.dll,告诉 PHP 加载这个用于连接 SQL Server 数据库的 PDO 扩展。
2.5 Zend 引擎 (Zend Engine)
Zend Engine 是一个开源的脚本引擎,用来解释执行 PHP 编程语言的代码。
简单来说,它是 PHP 背后的"核心程序",负责把 PHP 脚本变成计算机可以执行的操作。
Zend Engine 会把 PHP 代码转换成一系列的操作码(opcodes)。
每个操作码代表 PHP 代码中一个具体的操作,这类似于 JavaScript 的字节码(bytecode),PHP 先解析成抽象语法树,再编译成操作码,最后由 Zend 执行。

PHP 代码先经过 Lexicon scan(词法扫描) → Parse(解析) → Create Opcode(生成操作码) → Process Opcode(执行操作码)。
执行后的结果返回给客户端(浏览器)。
Zend Engine 会把操作码放入缓存,这样下次同样的 PHP 代码执行时,可以直接用缓存里的操作码,提高执行效率。
2.5.1 Zend Extensions(Zend 扩展)
通用 Zend 也有扩展。Zend 扩展是一组库(libraries),用来给 PHP 编程语言增加额外的功能。也就是在 PHP 核心功能之上,提供更多高级功能给开发者使用。
它扩展 PHP 核心功能,让 PHP 能做更多事情。
帮助开发者优化代码、提升安全性、改善应用程序的用户体验。
举例:OpCacheGUI 就是一个 Zend Extension,可以图形化查看 PHP 脚本缓存的状态,提高性能。

2.5.2 Just-In-Time (JIT) 编译
PHP 从 8.0 开始支持 JIT 编译,我们上一章介绍了 JS 的 JIT 编译。
JIT = Just-In-Time,即"即时编译",意思是 PHP 在运行时将代码直接编译成机器码,而不是像传统解释器那样一行一行解释执行。

- PHP Script: PHP 代码,例如 c = a + $b;
- Zend Opcache:PHP 会先把代码放进 Opcache(操作码缓存),避免每次请求都重新解析 PHP 代码。
如果操作码已经缓存,就直接使用缓存(Cached)。
如果没有缓存,则生成新的操作码(Not Cached)。 - Opcodes / Optimized Opcodes:
PHP 解析后生成的 操作码(Opcodes)。
Opcodes 可以通过优化器生成 Optimized Opcodes。 - Zend VM (Virtual Machine):
Zend VM 是 PHP 的虚拟机,用来执行操作码。 - Opcode Handlers → CPU:
Opcode 被 Zend VM 执行,最终转换为 CPU 可以理解的指令。
JIT 会在 Opcodes 被加载后,将 优化后的操作码(Optimized Opcodes) 再进一步编译成 机器码(Machine Codes)。
机器码可以直接被 CPU 执行,不需要每次都通过 Zend VM 执行。
这样在高负载或重复运算的场景下性能提升明显。
3. SQL
3.1 数据库
这部分在 CPT103 中有详细介绍。
3.1.1 数据库的基础概念
数据库是软件:类似于 Web 服务器(Web server),数据库(database)也是一种软件。
数据库的定义:数据库是有组织的结构化信息或数据的集合,通常存储在计算机系统中。
数据库管理:数据库通常由数据库管理系统(DBMS, Database Management System)来控制。
数据库系统:数据库本身的数据和管理它的 DBMS,以及相关的应用程序一起,组成了一个完整的数据库系统(Database System)。
名字上的混用:有时候,我们把某个数据库的名字直接称为 DBMS 的名字,例如 "MySQL" 既是数据库管理系统的名字,也通常指存储的数据。
3.1.2 数据库和电子表格(Spreadsheet)
下面的表格将两者进行了对比。
| 特性 | 电子表格(Spreadsheets) | 数据库(Databases) |
|---|---|---|
| 数据组织方式 | 数据以单元格(cell)组织 | 数据以复杂集合的方式组织,可以处理表之间的关系 |
| 数据量 | 可访问的数据量有限 | 可访问大量数据 |
| 用户访问 | 一次通常只有一个用户操作 | 支持多个用户同时访问 |
| 控制方式 | 数据由用户自己控制 | 由 DMS(数据管理系统)控制 |
| 数据录入 | 需要手动操作 | 数据录入严格且一致,保证数据完整性 |
数据库中的单元格可以存储关系型数据(表、记录)。
电子表格中每个单元格可以存放计算公式(如 Excel)。
数据库的计算通常在数据检索之后处理。
电子表格可以在视图里直接进行计算。
3.1.3 事务(Transaction)和 ACID 原则
3.1.3.1 事务(Transaction)
事务是一组数据库操作的集合,这些操作要么全部完成,要么全部不执行。

上图出示了一个例子:
BEGIN WORK:开始事务
SELECT ... FOR UPDATE:选择数据并锁定以进行更新
INSERT INTO table ...:插入新记录(可能有多条)
UPDATE table ...:更新记录
DELETE FROM table ...:删除记录
最后:
COMMIT WORK:提交事务,所有操作生效
ROLLBACK WORK:回滚事务,所有操作撤销
3.1.3.2 ACID 原则
ACID 是事务的四个关键特性:
| 缩写 | 中文 | 解释 |
|---|---|---|
| A: Atomicity | 原子性 | 保证事务内的所有操作要么全部完成,要么全部不执行。失败时回滚。 |
| C: Consistency | 一致性 | 事务执行前后,数据库都保持一致状态。 |
| I: Isolation | 隔离性 | 不同事务互不干扰,每个事务独立执行,外部看起来是顺序执行。 |
| D: Durability | 持久性 | 事务一旦提交,其结果会永久保存在数据库中,即使系统崩溃也不会丢失。 |
3.1.4 ER 图(Entity-Relationship Diagram)
ER 图是一种用来表示数据库中 实体(Entities) 及其 关系(Relationships) 的可视化工具。
实体(Entity)通常是数据库中的表(table)或者数据对象,比如"客户(customer)"、"商品(item)"。

矩形(rectangle):表示实体(Entity),例如 customer、item、producer。
菱形(diamond):表示实体之间的关系(Relationship),例如 buys(购买关系)。
椭圆形(oval):表示属性(Attributes),例如 item 的 price、description。
不同的实体通过关系连接,定义它们之间的逻辑联系。
例如:
customer 和 item 通过 buys 关系连接,表示客户购买商品。
item 与 producer 通过某种关系连接,表示商品由生产者生产。
属性则描述实体的特征,如 price 是 item 的属性。
ER 图帮助数据库设计者理解数据结构,明确每个数据实体的属性及它们之间的关系,从而更合理地设计数据库表结构和约束。
3.1.5 主键(Primary Key)和外键(Foreign Key)
- 主键 (Primary Key)
每张表中有一列或几列被指定为主键。
主键的作用是保证表中每一行数据的唯一性。
在图中,CUSTOMERS 表的 Customer No 列是主键,每个顾客都有唯一编号。 - 外键 (Foreign Key)
外键是用于建立和另一张表的关系的列。
它确保引用的数据在另一张表中存在,从而保持数据一致性。
在图中,ORDERS 表的 CustomerNo 列是外键,它引用 CUSTOMERS 表中的 Customer No 列。
这保证了 ORDERS 表中的每个订单都对应一个存在的顾客。
因此 CUSTOMERS 表没有外键,因为它不依赖其他表,它只是存储顾客信息。ORDERS 表的主键是 OrderNo。
总结一下:每张表通常都有主键(Primary Key),主键的作用是唯一标识表中的每一行数据,确保没有重复记录。几乎所有正规化的数据库表都会有主键。
外键(Foreign Key)是可选的,外键用于建立表与表之间的关联,它引用另一张表的主键(或唯一键),确保数据的完整性。
如果一张表不需要和其他表关联,它可以没有外键。
有外键的表通常是"子表",依赖于另一张"父表"。
3.1.6 数据库设计
我们现在回到数据库设计上,所以我们前面学习了 ER 图,它可以帮助我们梳理哪些是 Entity(实体),哪些是 Attributes(属性),以及 Entity(实体)之间的 Relationship(关系)。我们前面学习的主键(Primary Key)和外键(Foreign Key)也是数据库里表的关键组成。理解这些结构我们就可以开始进行数据库设计了。

所以现在我们这么开始设计一个数据库:
- 一张表 = 一个 Entity(实体)
例如 Customers 表就是一个实体,Orders 表也是一个实体。 - 表的列(Columns) = Entity 的 Attributes(属性)
每一列表示这个实体的一个特征,比如 Customers 的列有 customer_id、first_name、email。 - 表之间的关系(Relationship) = 外键(Foreign Key)
当两个实体之间存在联系时,使用外键把它们连接起来。
例如 Orders 表中有 customer_id 列,这个列是外键,指向 Customers 表的 customer_id(主键),表示订单属于哪个客户。 - 主键(Primary Key)
每个实体(表)都有主键,用于唯一标识每条记录。
外键指向的列通常是另一表的主键。
3.1.7数据库中的索引(Index)
B-Tree 是数据库常用的索引数据结构。

图中显示了一个数字索引:
顶部节点(17、35)是"分界点",用来快速决定往哪条子树查找。
每个叶子节点存储具体的数据(比如 3、5、9 等)。
查询原理:
如果要找数字 28:
从根节点 17、35 判断 28 大于 17 小于 35 → 下到中间子树。
在子树里继续查 → 找到 28。
优点:查找效率是 O(log n),比顺序扫描整个表快得多。
除了索引之外,当然还有全表扫描(ful table scan),它就会一项一项对比,因此比如要查找一个数据,可能就要搜索全部数据的一般,而在B-Tree 的帮助下数据库在查询和排序上相较于全表扫描(ful table scan)就会快速很多。B-Tree 适合范围查询和排序。
使用索引可以显著减少查找次数和查询时间,尤其是大表。
3.2 SQL(Structured Query Language,结构化查询语言)
现在我们开始讲SQL(Structured Query Language,结构化查询语言)。
SQL 是一种编程语言,专门用于操作关系型数据库(relational databases)。
SQL 可以用来:
- 查询数据(SELECT)
- 操作数据(INSERT、UPDATE、DELETE)
- 定义数据结构(CREATE TABLE 等)
- 管理访问权限(GRANT/REVOKE)
总结一下:SQL 就是与数据库"对话"的语言,它让我们可以读取、修改、组织和管理数据库中的数据。
3.2.1 SQL 命令的分类
SQL 里的语句大致分成 4 类,每一类负责不同事情。
| 类型 | 全称 | 作用 | 常见命令 |
|---|---|---|---|
| DDL | Data Definition Language | 定义和修改数据库结构 | CREATE, ALTER, DROP, TRUNCATE, RENAME |
| DML | Data Manipulation Language | 操作表里的数据 | SELECT, INSERT, UPDATE, DELETE |
| DCL | Data Control Language | 控制权限 | GRANT, REVOKE |
| TCL | Transaction Control Language | 控制事务 | COMMIT, ROLLBACK, SAVEPOINT |
3.2.1.1 DDL
DDL = Data Definition Language,中文叫 数据定义语言。
它负责的是 schema(模式 / 结构),也就是数据存放的框架,也就是:
- Create:创建
- Alter:修改结构
- Drop:删除
- Truncate:清空表数据
- Rename:重命名
它不是拿来操作表里的具体数据内容的,而是拿来定义"表长什么样"。
下面给出一些示例。
sql
CREATE DATABASE SCHOOL;
创建一个名叫 SCHOOL 的数据库。
sql
CREATE TABLE t_school(
ID INT PRIMARY KEY,
School_Name VARCHAR(40),
Number_Of_Students INT,
Number_Of_Teachers INT,
Number_Of_Classrooms INT,
EmailID VARCHAR(40)
);
在数据库里创建一张叫 t_school 的表,并定义它的字段和类型。
3.2.1.2 DML
DML = Data Manipulation Language,中文叫 数据操作语言。
它是用来操作表中的数据的,不是改表结构,包括:
- Select:查询数据
- Insert:插入数据
- Update:更新数据
- Delete:删除数据
下面给出一些示例。
sql
INSERT INTO t_school (...)
VALUES (...), (...), (...);
这是往 t_school 表里加入多条学校记录。
sql
SELECT * FROM t_school;
把表里的数据查出来。
sql
UPDATE t_school
SET School_Name = 'New Name'
WHERE ID = 1;
修改某条记录。
sql
DELETE FROM t_school
WHERE ID = 1;
删除某条记录。
3.2.1.2.1 WHERE
在操作数据时,WHERE 条件一定要写对。WHERE 类似于 CSS 选择器,但是 WHERE 是筛选目标的条件,从而指定操作哪一部分数据。因此条件必须写正确,不然可能查错、改错、删错很多数据。
下面给出一些示例。
- 查询
sql
SELECT * FROM table_name WHERE condition;
从 table_name 这张表里
查询满足 condition 条件的记录
- 表示所有列
sql
SELECT * FROM students WHERE id = 1;
表示查找 id = 1 的学生。
- 更新
sql
UPDATE table_name SET column_name = value WHERE condition;
在 table_name 表中
把某个字段 column_name 改成 value
只修改满足 condition 的记录
- 删除
sql
DELETE FROM t_school WHERE ID = 6;
从 t_school 表中
删除 ID = 6 的那条记录
3.2.1.3 DCL
DCL = Data Control Language,中文叫 数据控制语言。
它主要用来管理数据库的访问权限,也就是:
- 谁可以访问数据库
- 谁可以查询、修改、删除数据
- 谁没有权限做这些事
核心命令是 GRANT 和 REVOKE
- GRANT:授予权限
- REVOKE:撤销权限
很多数据库管理系统(DBMS),比如 MySQL,是独立系统,有自己的用户账户。
当我们想控制谁能访问 SQL 表中的数据时,就要用 DCL。
只有被授权的数据库用户才能访问表中的数据。
下面给出一些示例。
- 授予权限
sql
GRANT SELECT, INSERT ON school.students TO user1;
把 school.students 这张表的
查询(SELECT)和插入(INSERT)权限
授予 user1
- 撤销权限
sql
REVOKE INSERT ON school.students FROM user1;
撤销 user1 对这张表的插入权限
3.2.1.4 TCL
TCL = Transaction Control Language,中文叫 事务控制语言。
它是用来管理事务(transaction)的,也就是控制一组数据库操作是:
- 正式生效
- 撤销
- 或退回到某个中间点
包括:
- COMMIT:提交
- ROLLBACK:回滚
- SAVEPOINT:保存点
下面给出一些示例。
- 开始事务
sql
START TRANSACTION;
开始一个事务。
- 插入数据
sql
INSERT INTO t_school(...)
VALUES (...);
往 t_school 表里插入数据。
这时候数据已经改了,但还在事务里,还没有最终确认。
- 提交事务
sql
COMMIT;
确认前面的操作,让它永久生效
- 设置保存点
sql
SAVEPOINT ins;
在当前事务里设置一个叫 ins 的中间点。
- 删除数据
sql
DELETE FROM t_school WHERE ID = 3;
删除 ID = 3 的记录。
- 回滚到保存点
sql
ROLLBACK TO ins;
撤销保存点 ins 之后的操作。
所以刚才那条 DELETE 会被撤销,数据库会回到 SAVEPOINT ins 时的状态。
3.2.2 数据库的类型(Database types)
我们刚刚提到了关系型数据库,现在就来讲一下数据库有哪些类型。

3.2.2.1 SQL Databases
这是关系型数据库。
但有两种常见方向:
Relational(关系型)
- 数据按表(table)来存
- 有行、列
- 表和表之间可以建立关系
- 常用 SQL 查询
例子:
- MySQL
- PostgreSQL
- Oracle
- SQL Server
Analytical (OLAP)
- 更偏向 分析型数据库
- 适合做统计、报表、数据分析
- 重点不是频繁增删改查,而是大规模分析查询
3.2.2.2 NoSQL 数据库
NoSQL 不是"没有 SQL",而是"不只 SQL"。
也就是说,这类数据库不是传统关系型表结构为主,但不代表完全不能查询或不能用类似 SQL 的方式。
常见类型有:
-
Key-Value(键值型)
数据形式像:
key -> value
查找速度快
适合缓存、会话、简单配置
例子:Redis、Riak
-
Column-Family(列族型)
按列族存储数据
适合大规模分布式数据
常用于海量数据场景
例子:Cassandra
-
Graph(图数据库)
重点存"节点"和"关系"
很适合社交网络、推荐系统、路径分析
比如:用户 A 认识用户 B,用户 B 喜欢电影 C
-
Document(文档型)
数据以文档形式存储,常见是 JSON 风格
结构比传统表更灵活
适合字段变化多、半结构化数据
例子:MongoDB、CouchDB
3.2.2.3 主流的数据库管理系统(DBMS,Database Management System)
MySQL 是目前最流行的关系型数据库之一。
SQL Server 和 Oracle 也属于主流的关系型数据库。
PostgreSQL、DB2、Sybase、Access、SQLite 等也属于关系型数据库(SQL Databases)。
MongoDB(文档型)、Cassandra(列族型)、Redis(键值型)、Neo4j(图数据库)、ElasticSearch(搜索型)是非关系型数据库。
OceanBase、TD SQL、GaussDB、POLARDB、CynosDB、GoldenDB 等,也是 NoSQL 或分布式数据库。
3.3 连接数据库
对于个人数据库系统。
用户(User)通过数据库应用程序(Database Application)操作数据库。应用程序通过数据库管理系统(DBMS)管理实际存储的数据。最底层是数据库(Database),保存数据。
例子:Microsoft Access 或其他个人数据库软件。
重点:这是一个单用户或小型数据库使用场景,适合个人使用。
对于商业数据库。
数据存储在数据库(Database)里。数据访问通过数据库驱动(Database Driver)进行。用户端可能使用高级工具,如 RStudio,通过 odbc、DBI、dplyr 等包连接和操作数据库。数据库提供表(Tables)、视图(Views)、以及 SQL 引擎(SQL Engine)进行查询和操作。
重点:商业数据库通常是多用户、大规模数据库,用户通过驱动和接口访问,而不是直接操作数据库文件。
3.3.1 连接 MySQL
不同操作系统和场景下,MySQL 支持多种连接协议(transport protocols):
| 协议 (Protocol) | 传输方式 (Transport Protocol Used) | 适用平台 (Applicable Platforms) |
|---|---|---|
| TCP | TCP/IP 网络连接 | 所有平台(跨平台) |
| SOCKET | Unix 套接字文件(Unix socket file) | Unix 和类 Unix 系统 |
| PIPE | 命名管道(Named pipe) | Windows |
| MEMORY | 共享内存(Shared memory) | Windows |
DBMS 作为服务器端:负责管理数据库、处理请求、存储和检索数据。
客户端应用程序:通过这些协议连接到 DBMS 来访问数据。
不同数据库支持的连接方式不同,MySQL 支持上表中列出的四种协议。
比如 Windows 系统本地访问 SQL 默认可以用命名管道连接。
也可以通过 127.0.0.1 或 localhost 以 TCP/IP 连接,端口通常是 3306。
共享内存一般很少用。
对于 Linux 和 MacOS,MySQL 默认使用 Unix 套接字文件连接,文件通常在 /var/run/mysqld/mysqld.sock 或 /tmp/mysql.sock。
也可以通过 127.0.0.1 或 localhost 以 TCP/IP 连接。
3.3.2 数据库管理和开发工具
有多种数据库管理和开发工具可以选择。
- Navicat: 功能最全、最好用,但通常需要付费。
Navicat官网 - MySQL Workbench: 官方出品,免费且专业,适合进行复杂的数据库建模。
MySQL Workbench官网 - PHPMyAdmin: 基于网页的工具(Web Tool),不需要安装客户端软件,在浏览器里就能操作,常用于管理云服务器或网站后台。
3.4 软件架构模式
软件框架模式有两种------C/S(下载一个专门的 App 才能用) 和 B/S(直接打开浏览器就能用)。
- C/S 架构 (Client/Server - 客户端/服务器)
含义: 这种应用拥有自己独立的客户端软件。
例子: 刚刚提到的 Navicat 或者是你电脑上的微信、英雄联盟。
特点: 你必须先在电脑或手机上安装这个软件。它通常运行速度更快、功能更强大,因为它能直接调用你电脑的硬件资源。 - B/S 架构 (Browser/Server - 浏览器/服务器)
含义: 这是 C/S 的一种特殊形式,它利用现有的网页浏览器作为客户端。
例子: 所有的网页版应用,比如网页版微博、或者课件里提到的 PHPMyAdmin。
特点: 不需要安装专门的软件。只要有 Chrome、Safari 或 Firefox 等浏览器,输入网址就能用。它的好处是随时随地都能访问,不用担心版本更新问题。
| 特性 | C/S (Client/Server) | B/S (Browser/Server) |
|---|---|---|
| 客户端 | 专门的软件 (如 Navicat) | 通用浏览器 (如 Chrome) |
| 安装 | 需要下载安装 | 零安装,开箱即用 |
| 性能 | 通常更高、更稳定 | 受限于浏览器性能和网络 |
| 更新 | 客户端需要手动或自动升级 | 刷新页面即是最新版本 |
3.5 使用 PHPMyAdmin 创建一个数据库表(Table)
sql
CREATE TABLE `can302`.`user` (
`id` INT NOT NULL AUTO_INCREMENT,
`first` VARCHAR(20) NOT NULL,
`last` VARCHAR(20) NOT NULL,
`email` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
);
这里先创建一个名为 can302 的数据库,然后在数据库里建一个名为 user 的表,包含 4 列(字段)。
然后将 id 设置为主键 (Primary Key) 并且是 自动递增 (Auto Increment)。这意味着每增加一个新用户,系统会自动给它分配一个唯一的编号。
如果使用 PHPMyAdmin 进行上述操作,我们点击按钮后,后台 PHPMyAdmin 会把你的操作转换成标准的 SQL 语句。
3.6 使用 PHP 编程语言来操作数据库
PHP 操作数据库工具有两种:
- PDO (通用型工具): 它的最大优势是跨数据库。如果你今天用 MySQL,明天想换成 SQLite 或 PostgreSQL,你只需要改几行代码,而不需要重写整个程序。
- MySQLi (专用型工具): 它是专门为 MySQL 设计的(字母 i 代表 Improved)。如果你的项目百分之百只用 MySQL,它也是一个非常好的选择。
详细对比如下。
| 特性 (Features) | PDO (PHP Data Objects) | MySQLi (MySQL Improved) |
|---|---|---|
| 数据库支持 | 支持 12 种不同的数据库驱动 (如 MySQL, SQLite, Oracle 等) | 仅支持 MySQL |
| API 风格 | 仅支持 OOP (面向对象) | 支持 OOP + Procedural (过程化) |
| 连接难度 | 简单 | 简单 |
| 命名参数 | 支持 (可以用名字标记参数) | 不支持 |
| 对象映射 | 支持 | 支持 |
| 预处理语句 | 支持 (安全性更高) | 不支持 |
| 性能 | 快 | 快 |
| 存储过程 | 支持 | 支持 |
下面展示连接数据库的三种主流方式该怎么操作:
- PDO 方式 (面向对象)
php
$pdo = new PDO("mysql:host=localhost;dbname=database", 'username', 'password');
- MySQLi 过程化方式 (Procedural)
php
$mysqli = mysqli_connect('localhost', 'username', 'password', 'database');
- MySQLi 面向对象方式 (Object-Oriented)
php
$mysqli = new mysqli('localhost', 'username', 'password', 'database');
3.6.1 使用 PDO 操作数据库
PDO 就像一个"万能转接头",只要换一个驱动,就能连接 PostgreSQL, MySQL, ORACLE 等各种数据库,而不需要大规模重写 PHP 代码。

php
// 1. 配置
$dsn = "mysql:host=localhost;dbname=testdb"; // 如果用MySQL就这么写
$username = "root";
$password = "";
try {
// 2. 建立连接
$pdo = new PDO($dsn, $username, $password);
// 3. 执行查询
$sql = "SELECT * FROM table_name";
$stmt = $pdo->query($sql);
// 4. 循环处理
while ($row = $stmt->fetch()) {
echo $row['column_name'] . "<br>";
}
} catch (PDOException $e) {
// 5. 错误捕获
die("数据库连接失败: " . $e->getMessage());
}
PDO 不像 mysqli 那样有两种写法,它必须使用对象和类的方式来操作。
3.6.2 使用 mysqli 操作数据库
mysqli是一个PHP扩展,用于操作的数据库仅限于MySQL数据库,不支持其他类型的数据库。
php
// 1. 连接数据库
$conn = new mysqli("localhost", "username", "password", "myDB");
// 2. 检查连接
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
// 3. 执行 SQL 查询
$sql = "SELECT id, firstname, lastname FROM MyGuests";
$result = $conn->query($sql);
// 4. 处理并输出数据
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
echo "id: " . $row["id"]. " - Name: " . $row["firstname"]. " " . $row["lastname"]. "<br>";
}
} else {
echo "0 结果";
}
// 5. 关闭
$conn->close();
mysqli 既可以写成"面向对象"风格(像上面这样用 -> 符号),也可以写成"过程化"风格(直接调函数)。
4. 总结
一个典型的 Web 应用程序(B/S 架构)是如何从头到尾运行起来的呢?
如下图所示。

这个系统由四个关键节点组成,它们通过不同的"语言"(协议)进行对话:
- 浏览器 (User/Client) → H T T P \xrightarrow{HTTP} HTTP Apache (Web Server)过程:你在浏览器输入网址或点击按钮,浏览器发出一个 HTTP 请求 给 Web 服务器(Apache)。
- Apache(Web 服务器软件) → C G I \xrightarrow{CGI} CGI PHP (Application Logic)过程:Apache 发现这是一个动态网页,于是通过 CGI(通用网关接口)把任务交给 PHP 引擎去处理。
- PHP → D B P r o t o c o l \xrightarrow{DB\ Protocol} DB Protocol MySQL (Database)过程:PHP 执行代码(就是你之前看到的 mysqli 或 PDO 代码),通过数据库协议去问 MySQL 要数据。
- 数据回传:MySQL 把数据给 PHP → \rightarrow → PHP 把数据塞进 HTML 模板给 Apache → \rightarrow → Apache 把完整的网页通过 HTTP 传回给你的浏览器。
4.1 HTTP 协议
当"客户端"(浏览器)向"服务器"(Apache)发送信息时,需要决定用哪种方式把数据传过去。
GET和POST的区别

| 特性 | GET 方法 | POST 方法 |
|---|---|---|
| 可见性 (Visibility) | 在 URL 中可见(直接显示在地址栏) | 在 URL 中不可见(封装在 HTTP Body 中) |
| 长度限制 (Length) | 有限制(通常约为 255 个字符) | 没有限制(数据放在 Body 中提交) |
| 性能 (Performance) | 性能较好(结构简单,直接附加在 URL 后) | 相对较低(由于需要处理和包含 Body 数据) |
| 数据类型 (Data Types) | 仅支持 字符串 (String) 类型 | 支持多种类型:字符串、数字、二进制、JSON 等 |
| 书签 (Bookmark) | 可以被收藏为书签 | 不可以被收藏为书签 |
| 缓存 (Cacheable) | 经常可以被浏览器缓存 | 很难被缓存 |
| 历史记录 (History) | 参数会保留在浏览器历史记录中 | 参数不会保存在浏览器历史记录中 |
无论是 GET 还是 POST,一个完整的请求都包含:
请求行 (Request Line):包括请求方法(GET/POST)、请求路径(Path)和协议版本(HTTP/1.1)。
请求头 (Request Headers):一堆属性对(如 Host, User-Agent 等),告诉服务器浏览器的情况。
消息体 (Message Body):只有 POST 才有,用来存放真正要提交的数据。

| 组成部分 | GET 请求示例 | POST 请求示例 |
|---|---|---|
| 数据位置 | 在"请求行"的 URL 后面 。你会看到 ?user=jhon&pass=java。 |
在底部的"消息体 (Message Body)"中。URL 路径保持干净。 |
| 请求头 (Headers) | 包含浏览器信息(Host, User-Agent 等),但不涉及提交的数据。 | 除了浏览器信息,通常还包含描述 Body 长度和类型的字段(如 Content-Length)。 |
| 隐秘性 | 极低。数据直接暴露在报文的第一行,也会显示在浏览器地址栏。 | 较高。数据被包裹在报文内部,不会直接显示在地址栏中。 |
4.2 PHP 处理 HTML 表单(Form)提交的数据
这一步将前端 HTML 和后端 PHP 代码串联起来。

绿色框是 HTML 代码,是用户在浏览器看到的界面。使用 <form> 标签,其中的 method 属性决定了是用 GET 还是 POST 发送数据。action="xyz.php" 则告诉浏览器要把数据发给哪个 PHP 文件。
蓝色框是 PHP 代码,是服务器端的处理。PHP 使用特殊的超全局变量来接收数据:
- 如果 HTML 里是 method="GET",PHP 就用 $_GET["..."] 拿数据。
- 如果 HTML 里是 method="POST",PHP 就用 $_POST["..."] 拿数据。
拿到数据后,你可以用它来做身份验证(登录)或者存入数据库(保存)。
GET具有灵活性,我们可以直接在 URL 后面拼接参数(比如 ?id=1),不需要表单也能产生 GET 请求。
<form> 具有双重能力:表单既可以发 GET 也可以发 POST,取决于你代码里的设置。
一个 HTTP 请求能同时包含 GET 和 POST 参数吗?
可以。 如果 action 链接里带了参数(如 action="save.php?id=123"),而表单 method 是 POST,那么 PHP 可以同时通过 _GET\['id'\] 拿到 URL 里的值,并从 _POST 拿到表单里的值。
4.2.1 Cookie 和 Session
HTTP 是无状态的 (Stateless),但我们可以利用 Cookie 和 Session 来解决这个问题,实现"登录状态保持"。
无状态的 (Stateless)是说服务器就像一个"健忘症患者"。我们刚发完一个请求,它处理完就瞬间把我们忘了。下次我们再来,它并不知道我们是刚才那个人。
Cookie(存储在客户端/浏览器)是一小段文本文件。服务器给浏览器发一个"通行证"(ID),浏览器存起来。下次请求时,浏览器自动带上这个通行证。
当然这也会带来风险,因为存放在我们的电脑里,容易被恶意代码读取或篡改,所以不安全。
Session(存储在服务器端)是存在服务器上的详细信息表(如用户名、登录时间)。
每个 Session 都有一个唯一的 Session ID。服务器把这个 ID 通过 Cookie 发给浏览器,但具体的敏感数据留在服务器的安全区域。
下图展示了这个流程。

- 登录请求:用户发送用户名和密码。
- 创建会话:服务器验证成功,生成一个 SESSION ID,并在数据库/内存里记录该 ID 对应的是用户 "john"。
- 颁发通行证:服务器在 HTTP Header 里写上 Set-Cookie: SESSIONID=...,发还给浏览器。
- 再次访问:用户访问新页面,浏览器自动带上 Cookie: SESSIONID=...。
- 识别身份:服务器拿着这个 ID 去数据库里一查:"哦,这是 john,给他看他的私人物品。"
4.2.1.1 使用 PHP 代码在浏览器中设置和读取 Cookie
- 设置 Cookie(setcookie.php)
php
<?php
// 设置过期时间:当前时间 + 1天的秒数 (24小时 * 60分 * 60秒)
$expire = time() + 60 * 60 * 24;
// 创建一个名为 "guest" 的 Cookie,值为 "007"
setcookie("guest", "007", $expire);
echo "Cookie has been set!";
?>
这段代码负责向浏览器发送一个名为 guest 的 Cookie,有效期为 1 天。
- 读取 Cookie(getcookie.php)
php
<?php
// 检查名为 "guest" 的 Cookie 是否存在
if (isset($_COOKIE["guest"])) {
// 如果存在,通过超全局变量 $_COOKIE 获取它的值
echo "Fetch the guest name from cookie: " . $_COOKIE["guest"] . "!<br>";
} else {
// 如果不存在(比如已过期或被手动删除)
echo "No cookie!";
}
?>
这段代码负责检查浏览器是否带回了该 Cookie,并将其值打印出来。

4.3 使用 PHP Session 开发一个完整的登录与登出系统
下面出示一个示例,展示完整的使用 PHP Session 从而登录和退出。
- 登录页面代码
php
<?php
session_start(); // 必须放在最顶部,开启/重用 Session
// 假设你已经建立了数据库连接 $con
// $con = new PDO("mysql:host=localhost;dbname=your_db", "root", "");
if (isset($_POST['login'])) {
$username = $_POST['username'];
$password = $_POST['password'];
// 注意:这里的 SQL 存在安全隐患(SQL 注入),仅用于实验演示
$sql = "SELECT count(*) FROM user WHERE username='$username' AND password='$password'";
// 执行查询并获取行数
$count = $con->query($sql)->fetchColumn();
if ($count == 1) {
// 登录成功,将用户名存入 Session
$_SESSION['username'] = $username;
} else {
// 登录失败,弹出提示
echo '<script type="text/javascript">alert("Username or password error!");</script>';
}
}
function login() {
if (!empty($_SESSION['username'])) {
// 如果已经登录,显示欢迎语和登出链接
echo "<h2> Welcome " . $_SESSION['username'] . ". <a href='logout.php'> logout </a> </h2>";
} else {
// 如果未登录,显示登录表单 (使用 Heredoc 语法)
echo <<<EOT
<h2></h2>
<div class="col-lg-8" style="text-align:center">
<form action="" method="post">
<h2 class="form-login-heading text-center"> Login demo </h2>
<br>
<input type="text" class="form-control" placeholder="Username" name="username" required>
<br>
<input type="password" class="form-control" placeholder="Your Password" name="password" required>
<br>
<button class="btn btn-primary" type="submit" name="login">Login</button>
</form>
</div>
EOT;
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CAN302 Login Demo</title>
</head>
<body>
<?php login(); ?>
</body>
</html>
它包含了 PHP 逻辑(验证登录)和 HTML 界面(显示表单或欢迎语)
- 登出页面代码
php
<?php
session_start(); // 必须先开启才能销毁
session_destroy(); // 销毁所有服务器端的 Session 数据
$url = $_SERVER['HTTP_REFERER']; // 获取来源页面的 URL
// 弹出提示并跳转回之前的页面
echo "<script type='text/javascript'>alert('logout success!'); window.location.href='$url'; </script>";
?>
4.3.1 SQL 注入(SQL Injection)
这是一种网络攻击。
攻击者在密码框输入了 'x' OR 'x' = 'x'。
原本的 SQL 逻辑变成了 WHERE username='Administrator' AND password='x' OR '1'='1'。
由于 'x' = 'x'(或者常见的 '1' = '1') 永远成立,数据库会认为验证通过,攻击者无需知道真实密码就能以管理员身份登录。
解决方式可以使用 PDO 预处理。
下面的代码展示了正确且安全的做法。它不再使用字符串拼接,而是使用了 PDO 的预处理语句 (Prepared Statements)
php
<?php
// 假设 $con 是你已经创建好的 PDO 数据库连接对象
if (isset($_POST['login'])) {
// 假设 mypost 是你自定义的过滤函数,或者直接用 $_POST['username']
$username = mypost('username');
$password = mypost('password');
// 1. 使用问号 (?) 作为占位符,不直接拼接变量
$sql = "SELECT count(*) FROM user WHERE username = ? AND password = ?";
// 2. 预处理 SQL 模板 (Prepare)
// 此时 SQL 语句已经发送给数据库,但参数还没传过去
$query = $con->prepare($sql);
// 3. 执行并传入参数 (Execute)
// 即使 $password 包含 'or '1'='1,也会被当成纯字符串处理,不会被当作命令执行
$query->execute(array($username, $password));
// 4. 获取结果
$count = $query->fetchColumn();
if ($count == 1) {
// 验证通过,记录 Session
$_SESSION['username'] = $username;
} else {
// 验证失败
echo '<script type="text/javascript">alert("Username or password error!");</script>';
}
}
?>
这段代码修复了之前直接拼接字符串导致的 SQL 注入漏洞,是处理登录逻辑的标准工业写法。






