我重构《我讨厌PHP-PHP和MySQL初学者指南》第三版
《我讨厌PHP-PHP和MySQL初学者指南》
作者:Geticer 2022-2026
# 前言
## 首先,我的背景和为什么会有这本书?
我写代码有年头了。从最早的HTML表格布局,到后来的CSS,再到各种后端语言。PHP?说实话,第一次见到它的时候,我的反应是:“这玩意儿语法怎么这么奇怪?”
但你知道吗?就像那个总爱迟到却总能完成任务的同学一样,PHP虽然有时候让人头疼,但它确实能干活。而且干得还不错。
这本书不是那种正襟危坐的技术手册。我们不会从“PHP的历史与发展”开始(那太催眠了)。我们会从一个实际问题开始:帮“奶油百吉饼公司”建一个能接订单的网站。
## 那么,这本书是为谁写的呢?
这本书是为那些找不到对象的……等等……拿错了……
《我讨厌PHP》是为那些一直想知道“PHP能为他们做些什么”的站长和设计师准备的;那些**真正想用PHP“做”一些东西**而不是外包出去的人。
你最好要熟悉HTML和能写/看懂HTML代码。
你不需要成为一个“我都是用记事本写代码”的那种人——只要能打开文本编辑器并理解代码就足够了。复制/粘贴知识也是有帮助的。
与市场上的其他 PHP 书籍不同,我们不会涵盖 PHP 语言广阔世界的每一个方面。
我们要脚踏实地的掌握 PHP 语言的基础知识,熟悉它,并在实际生活中的例子中使用它……而不是“填鸭式教育”。
本书也适合在看《跟Wakaba酱一起学网站制作》到## 第六章的时候阅读,可替代该章节。
**重要声明:本书采用“先让它跑起来,再让它跑得更好”的教学法。这意味着前几章我们可能会写一些“不那么完美”的代码。别担心——后面我们会一起修复它们。**
### 本书概述
我们将用“奶油百吉饼公司”的案例贯穿全书:
- **Part 1**:让他们有个能显示动态信息的网站
- **Part 2**:让网站能记住用户和订单
- **Part 3**:用数据库管理所有订单
- **Part 4**:让一切变得更安全
- **Part 5**:让网站上线并优化
每个部分都有实际可运行的代码,你随时可以停下来试试。
## 更多学习的地方
本书只是带你进入PHP世界的第一步,更多的路得自己走,以下网站会对你有很大帮助。
- php.net
- Google
- Bing
- StackOverflow
- 知乎
- 掘金
以下书籍也会对你有帮助:
- 《跟Wakaba酱一起学网页制作》
- 《跟Wakaba酱一起学Git使用》
# 第一章:简单的东西
*涵盖Echo、字符串、Date()、变量、Include、Require和Header()。*
在我们开始学习如何使用 PHP 之前,我们需要简单地探讨一下什么是 PHP……但我不会去讲一些冗长的关于 PHP 的历史,以及为什么至今还在用。
> 另外,**在继续学习之前,最好熟悉一下HTML**,因为你需要能够在网页中复制和粘贴代码。
---
在广阔的网络世界中,有两种类型的编程语言——“服务器端语言”和“客户端语言”。
前者是*在 Web 服务器*上运行,后者是*在你的浏览器*中运行。
> **你可以这样想:**
>
> **服务端**:你在一家餐馆点了一个肉夹馍。它来到你面前时就是做好的,可以吃的。
>
> **客户端**:你在一家餐馆点了一个肉夹馍。肉夹馍的各个部分(馍、肉馅、调味品等)被分别送到你手中,你需要在吃之前把它们组装起来。
PHP是一种**服务器端语言**。所有的处理都是在 Web 服务器上完成的,其结果是以 HTML 的形式传递给 Web 浏览器的(顺便说一下,HTML是一种客户端**标记语言**)。
大多数编程书会用一整章的篇幅来讲安装。我们只会用两句话:
- 如果你有网络主机,它很可能已经装了PHP,仅需把代码上传到主机即可。
- 如果想在本地测试,装个XAMPP或MAMP(具体步骤见附录A)也可以运行。
> **需要注意的事项:**
>
> 1. 文件后缀名**必须是 `.php` **而不是 `.html`。如果后缀是 `.html`的话,PHP会忽略掉其中的PHP代码的。*(也有例外情况,但现在我们不谈这些)*
> 2. PHP代码**必须在 `` 标签内**。如果不这样做,PHP会忽略掉的。
> 3. 每个PHP语句必须以分号结尾。如果不这样做,你会得到一些“神秘的”错误信息。
## 现在,让我们开始编程吧
我们将从基础开始。让PHP将内容输出到浏览器、什么是变量以及如何使用它们……几乎任何页面都会用到的例子。
让我们从最基本的一个开始。
## Echo语句
我们要告诉PHP向屏幕上输出一些东西。**请记住,PHP可以和HTML一起出现和使用**,但为了保持简单,我们在本例中不用HTML。
```php
```
我们来分解一下:
1. `` - “好了,切回HTML模式”
> **为什么不用"Hello World"?**
> 因为每本书都用"Hello World",我想换换口味。而且,我们的客户确实叫“奶油百吉饼公司”。
#### 本小节常见问题
#### 我直接看到了HTML或PHP源代码怎么办?
- 尝试访问 `http://localhost/index.php` 而不是直接双击文件打开
- 如果使用本地环境,确认XAMPP/WampServer正在运行,记得点击“Start”按钮。
- 如果使用服务器,请确保所购服务器是虚拟主机(自带PHP),或如果是VPS主机的话,确保PHP正确安装并启动。
#### 嗯嗯,但如果我想在我的屏幕上看到英文引号怎么办?
假设你想显示:
```
"今日特价",老王说。
```
直接写会出错:
```php
// 错误!
```
需要“转义”引号:
```php
// 正确!
```
`\"`告诉PHP:“这是个普通的引号,不是代码的一部分”。
> 而中文的单双引号不需要转义。
## 字符串
字符串就是**一串文字**。在PHP中,字符串用引号包裹。
```php
```
双引号和单引号有什么区别?
- 双引号:可以包含变量和特殊字符(如`\n`换行)
- 单引号:所有内容都按字面意思显示
推荐新手用**单引号**,省去转义的麻烦:
```php
```
#### 字符串内的特殊命令…
有些特殊字符可以改变显示效果:
- `\n`:换行(在HTML中要用`
`才有效)
- `\t`:制表符(相当于按Tab键)
- `\\`:显示一个反斜线
## 变量
变量是**存放数据的容器**。你可以把它想象成便利贴——写个名字,贴个值。
变量是相当强大的,如果你搞砸了,它们会在半夜来找你的。
声明一个变量很容易。
```php
```
只要遵守四条基本规则,你想怎么起变量名都行:
1. 以`$`美元符号开头
2. 可以包含字母、数字、下划线
3. 不能以数字开头
4. 区分大小写(`$Price`和`$price`是两个变量)
在你说 *“我需要这个干什么?”* 之前,请记住,**变量是非常有用的**。
> **命名建议**:
> 用英文,有意义。比如:
> - 好:`$user_age`, `$product_price`
> - 不好:`$a`, `$xx`
### 变量的实际应用
```php
奶油百吉饼公司
欢迎来到!
今天是:
经典百吉饼:元/个
```
### 输出多个变量的内容
用点`.`连接变量:
```php
```
输出:
```
你购买了 3 个物品。
```
#### 扩展阅读:常量
常量是**一旦定义就不能改变**的值。比如圆周率π等。
```php
";
echo SITE_NAME;
?>
```
将会输出:
```
3.1415926
奶油百吉饼公司
```
常量通常用**全大写**,以便和变量区分。
#### 扩展阅读:PHP预定义的变量,与超全局变量
PHP已经准备好了一些变量给你用,最常用的是`$_SERVER`:
```php
";
echo "当前页面:" . $_SERVER['PHP_SELF'] . "
";
?>
```
上面例子的结果是:
```
您的IP地址是:192.168.110.22
当前页面名称是:/第一章:简单的东西/变量/扩展阅读:PHP预定义的变量,与超全局变量.php
你来自这个页面:https://creamy-bagels.example.com/第一章:简单的东西/变量/扩展阅读:PHP预定义的变量,与超全局变量.php
```
可以使用以下超全局变量:
| 变量名称 | 变量描述 |
| ------------- | ----------------------------------------------- |
| $_GET | HTTP GET请求的信息 |
| $_POST | HTTP POST请求的信息 |
| $_FILES | HTTP上传的文件信息 |
| $_SERVER | 服务器和环境信息 |
| $_COOKIE | HTTP Cookies |
| $_SESSION | 会话变量 |
| $_REQUEST | HTTP请求变量,包含$_GET、$_POST和$_COOKIE的内容 |
| $GLOBALS | 全局作用域中的所有变量,集合成一个数组 |
这些变量是**数组**,用`[ ]`和键名访问值。我们后面会详细讲数组。
## 包含文件
假设你正在开发一个有5个页面的网站,你可能会增加一些页面。你的页面HTML看起来像这样。
请修改index.php文件为以下内容:
```html
奶油百吉饼公司
是时候让你体验一下更好的百吉饼了吗?
欢迎来到奶油百吉饼公司
```
而导航栏要在每一个页面中出现。
现在,当你在你的网站上添加一个页面时,你就得改变所有5个页面的代码,添加新的链接。这需要大好几分钟的时间……
那如果网站有到20个页面或更多呢?这将是一场恶梦!
**使用`include`吧!**
让我们把上面的例子中的菜单链接代码剪切下来,粘贴到一个新的文本文件中,命名为`nav.php`。
```html
```
将以下内容粘贴到原来文件中的导航栏代码位置…
```php
```
这样,想要修改导航栏的时候,只改`nav.php`一个文件就行!
同样的,页脚也可以用`include`处理。
创建`footer.php`:
```php
```
现在`index.php`可以很简单:
```php
奶油百吉饼公司
是时候让你体验一下更好的百吉饼了吗?
欢迎来到奶油百吉饼公司
```
### 我**要求**你包含这个文件
`require`和`include`很像,但更严格:
- `include`:如果文件找不到,显示警告,然后继续执行
- `require`:如果文件找不到,显示错误,停止执行
```php
// 配置文件必须存在
// 小部件可有可无
```
还有它们的“只包含一次”版本:
- `include_once`
- `require_once`
防止重复包含同一个文件造成问题。
## 现在,让我们在一个例子中把这一切结合起来吧
现在,我们要在网站里面显示广告。你还想在广告代码上测试不同的颜色主题,或定期改变广告。
问题开始充斥着你的头脑……
- 我怎样才能轻松地改变广告的颜色?
- 我怎样才能让其他人(下一个维护者)轻松地输入新的广告代码?
因此,我们将定义以下变量:
```php
```
将以上内容保存成`settings.php`。
然后,再创建呈现广告的`ad.php`,使用之前定义的变量:
```php
```
然后,我们回到`index.php`,添加广告:
```php
```
```php
这里是奶油百吉饼公司。
```
刷新一下浏览器里的页面,你会看到广告已经显示出来了。
如果之后决定要改变广告的颜色或广告内容,你只需修改**一个文件**——`settings.php`即可!
## 使用`header()`进行重定向
你可以用PHP来重定向到其他页面或网站。
```php
```
当访客访问这个PHP页面时,他们将被重定向到代码中的`location`部分写的链接。
常见用途:
1. 用户登录后跳转到会员页面
2. 旧页面迁移到新地址
3. 根据条件显示不同内容
## 第一章 小结
这章我们学了PHP的“生存必备技能”:
- `echo`:显示内容
- 字符串:处理文字
- 变量:存储数据
- `include`/`require`:复用代码
- `header()`:跳转页面
记住:PHP代码在服务器上运行,用户**看不到**你的代码,只能看到结果。
## 问答时间!
> 1. `include`和`require`有什么区别?
> 2. 如何在一个字符串中显示双引号?
> 3. 为什么说变量是“数据的容器”?
非常好。现在,短暂的休息一下……然后继续下一章!
# 第二章:不简单的东西
*涵盖`if`、`else`、`switch`、数组、函数、Session和Cookie。*
## 你已经通过了问答——好耶!
现在,你已经掌握了足够的PHP知识,可以在你的网站中添加基本的PHP功能。
知道很多其他人不知道的事情是非常酷的,而且他们永远无法通过右键你的网站选择“查看源代码”来发现(尤其是 你无法通过在浏览器中查看源代码 来看到PHP代码!)。
---
在本节中,我们将介绍一些更高级的PHP代码。这些东西你现在可能还用不到,但一旦你熟悉了PHP并想从中获得更多,你就准备好了,我的年轻徒弟。
在我们继续学习之前,你需要能够——
## 在代码中添加注释
**为什么?** 如果你添加了注释,那么你就会更容易知道你当时为什么要写某一行代码……这样你就不会在3个月后回来问自己:*“我写这段代码的时候是不是睡着了?”*
以下是注释代码的例子。
```php
```
PHP 会忽略掉以上内容。
注释不只是“写给自己看的小纸条”,它更是你和未来自己(或者接手你代码的同事)沟通的桥梁。比如:
```php
// TODO 写个更有难度的示例和对应注释
// 检查用户是否已登录,避免未授权访问订单页面
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}
```
这行注释清楚地说明了“为什么要跳转”——不是随便写的,而是为了安全。三个月后你再回来看这段代码,一眼就知道它的目的,不用重新推理逻辑。
现在,我们继续学习PHP!
## 如果这样就好了——`if`语句
PHP中的`if`语句和我们在生活中做判断很像。比如:
- **如果**今天下雨,我就带伞出门。
- **如果**今天是周末,我就多睡一会儿。
首先,我们来看看PHP是如何比较条件值的。你会在IF语句中看到“运算符”。
- `==` `===` 等于
- 强烈建议使用 `===`,它同时检查值和类型
- `!=` `!==` 不等于
- 强烈建议使用 `!==`
- `<` 小于
- `>` 大于
- `<=` 小于等于
- `>=` 大于等于
实例:
```php
```
如果现在是午夜0点到上午11点之间的某时,会显示“上午好!欢迎光临奶油百吉饼公司,早餐百吉饼热卖中!”。如果是下午的话就不会显示东西了。
### 扩展阅读:组合判断条件
当判断条件不只有一条的时候,可以使用以下运算符组合起来:
- `&&` 与:所有的判断条件都成立,才会执行里面的代码
- `||` 或:只要有一个判断条件成立,就执行里面的代码
- `()` 括号:先判断括号里的条件(改变判断顺序),在一些情况中很有用(括号可以嵌套)
*“诶?为什么要在条件判断里用这些奇怪的符号呀?”*
其实呢,就像百吉饼店的营业时间一样,有时候光看“是不是周末”还不够,还要看“是不是在营业时段内”呢!如果只用一个条件,就像只尝一种口味的百吉饼——虽然也不错,但组合起来才更有趣呀!
> 表格:营业时间
>
> | 周几 | 营业时间 |
> | --- | --- |
> | 周一到周五 | 8点到20点 |
> | 周六、周日 | 10点到16点 |
则对应的代码表示为:
```php
$isWeekend = (date("N") >= 6); // 判断是否为周六或周日
$hour = date("H"); // 当前小时(24小时制)
if ($isWeekend && $hour >= 10 && $hour <= 16) {
// 如果是周末,且在10点到16点之间:周末午间特惠
echo "周末午间特惠:买二送一!";
} elseif ($hour < 8 || $hour > 20) {
// 如果是工作日(上一个if里已经将周末排除了),且不在8点到20点之间:非营业时间
echo "非营业时间,请稍后再来。";
} else {
// 其他时间:营业时间
echo "欢迎光临奶油百吉饼公司!";
}
?>
```
还有……
- `!` 非(取反)
```php
```
## 用`else`扩展`if`的内容
如果你只需要检查**一个**条件,IF/ELSE语句是非常好的。
但是,如果你需要依次检查**多个**条件呢?
比如说,我们还想在下午和晚上显示不同的问候呢?可以用`else`:
```php
```
根据当前时间,会显示不同的问候。可能会输出:
- 从0点到11点之间,会显示“上午好!早餐百吉饼热卖中!”
- 从12点到23点之间,会显示“下午好!欢迎来份下午茶百吉饼套餐!”
## 更多条件?用`elseif`
如果还想区分下午和晚上呢?那就用`elseif`:
```php
```
> 注意:`elseif`是一个词,不是分开的`else if`。另外,**没有`if`就不能用`elseif`**。
可能会输出:
- 从0点到11点之间,会显示“上午好!早餐百吉饼热卖中!”
- 从12点到17点之间,会显示“下午好!欢迎来份下午茶百吉饼套餐!”
- 从18点到23点之间,会显示“晚上好!夜宵百吉饼也有供应哦!”
## 是时候‘切’换思路了!
如果你有很多个`elseif`,代码会变得冗长。这时可以用`switch`来简化。
假设我们要根据用户选择的百吉饼口味显示不同的推荐搭配:
```php
```
---
当你使用`Switch`时,请确保包含 `break`语句——如果没有的话,将会继续执行下一个 `case`里的代码,直到出现 `break` 或整个 `switch`结束。
在一些情况中,这种 *你可能会觉得“反直觉”* 的设计是很有用的:
```php
```
这样,当`$category`是“狗”的时候:
- 进入第一个case`"早餐"`里,
- 里面没有`break`,
- 所以会进入到下一个case`"午餐"`里,
- 里面也没有`break`,
- 所以会继续进入到下一个case`"套餐"`里,
- 这里有一个`echo`命令和一个`break`,
- 所以会执行`echo`命令,
- 再跳出`switch`。
最终结果是:
```
此类订单满50元免配送费!
```
-----
另外,请注意,当我们选择的口味“提子”不在任何一个`case`里,并没有语句兜底!所以什么都不会输出。
我们需要在`Switch`中再加一个东西——`default`。
```php
```
这样一来,如果没有匹配的 `case`,就会显示我们的默认值。
```
推荐搭配:试试我们的招牌套餐吧!
```
## 数组
数组是**能装多个值的变量**。
就像是你的“购物清单”,你可以把多个相关的值放在一个变量里,方便统一管理。比如你要列出所有口味的百吉饼,如果不用数组,你可能得这样写:
```php
$flavor1 = "原味";
$flavor2 = "全麦";
$flavor3 = "蓝莓";
```
这样不仅变量多,而且很难批量处理。如果用数组:
```php
';
echo $flavors[2];
?>
```
数组就像是你的百吉饼点单小本子。你不用给每个口味单独起个名字(比如 flavor1、flavor2……),只要把它们全写在一个清单里就行。这样,你想看看今天有哪些口味、加个新口味、或者找找有没有“蓝莓味”,都特别方便。
而且,从用户在网页上填的订单信息,到后台数据库里存的所有顾客记录,PHP 几乎都是用数组来“打包”和“传递”这些信息的。可以说,学会了数组,你就拿到了 PHP 世界的一把万能钥匙!
> 以上例子将会输出:
>
> ```
> 原味
> 蓝莓
> ```
数组使用键(key)确定要引用的值(value),例如:
```php
$array[key] = value;
```
键通常从"`0`"开始,因为PHP喜欢从零而不是从一开始编号。*不要问为什么。*
### 关联数组
普通数组像是按顺序编号的储物柜(0号、1号、2号……),而关联数组更像是贴了标签的收纳盒——你不用记“第几个”,直接喊名字就行!
比如描述一个百吉饼,你可以这样写:
```php
"经典原味", // 名称
"price" => 15, // 价格
"calories" => 250 // 卡路里
];
echo $bagel_info["name"];
echo '
';
echo $bagel_info["price"];
echo '
';
echo $bagel_info["calories"];
?>
```
现在,你想知道价格?直接问 $bagel_info["price"] 就行!是不是比记“它是第几个”直观多了?这种“键值对”的方式,让代码读起来就像在说话,特别适合存储有明确属性的信息。
以上例子将会输出:
```
经典原味
15
250
```
### 多维数组(二维数组、三维数组等)
多维数组是**包含**一个或多个其他**数组的数组**。除了令人困惑之外,它们也不经常使用,但我们会讲一个简单的例子,这样你就知道它们是如何工作的。
```php
["原味", "全麦", "蓝莓"],
"drinks" => ["咖啡", "茶", "果汁"],
"combos" => ["早餐套餐", "下午茶套餐"]
];
?>
```
在这个例子中,我们有一个名为“`$menu`(菜单)”的二维数组,它包含三个不同的数组(`bagels`(百吉饼)、`drinks`(饮品)和`combos`(套餐)),每个数组都包含自己的三个数组值。
这意味着你可以这样获得“全麦”:
```php
```
你现在可能看不到数组多有用,但我认为(并希望)一旦我们进入下一节——**循环**,它就会变得更有意义。
让我们继续前进!
## 循环
FIXME 没想好怎么开始这篇
PHP里有好几种循环,我们一个一个来看~
### While 循环
“只要……就一直……”
FIXME 用生活中的例子解释While循环
```php
";
$i++;
}
?>
```
会输出:
```
正在烤第1个百吉饼~
正在烤第2个百吉饼~
正在烤第3个百吉饼~
正在烤第4个百吉饼~
正在烤第5个百吉饼~
```
只要 `$i` 小于等于5,就会一直循环下去。`$i++` 就是让 `$i` 每次加1,不然就会……一直烤百吉饼,永远停不下来!(好可怕)
##### 扩展阅读:Do While循环
“如果第一次就想先执行怎么办?”
这时候就要请出 `do while` 循环啦!它和 `while` 很像,但是它会先执行一次do里面的代码,再判断条件!
```php
";
// 吃完一个,可能还饿,可能不饿了
$hungry = false; // 但假设我们吃完就不饿啦
} while ($hungry);
echo "吃饱了,满足~";
?>
```
即使一开始 `$hungry` 就是 `false`,也会先执行一次“吃百吉饼”哦!
#### For 循环【最常用之一】
“如果要精确控制循环次数呢?”
FIXME 写生活化的代码例子
非常适合你知道要循环多少次的时候用!
#### Foreach 循环【最常用之二】
“如果要遍历数组里的每个元素呢?”
`foreach` 就是为数组量身打造的!
FIXME 写生活化的代码例子
```php
";
}
?>
```
输出:
```
我们有原味口味的百吉饼哦!
我们有蓝莓口味的百吉饼哦!
我们有巧克力口味的百吉饼哦!
我们有全麦口味的百吉饼哦!
```
如果是关联数组,还可以同时拿到键和值:
```php
15,
"蓝莓" => 18,
"巧克力" => 20
];
foreach ($menu as $item => $price) {
echo $item . "百吉饼:" . $price . "元
";
}
?>
```
#### 扩展阅读:跳出循环
“如果中途想提前结束怎么办?”
就像烤饼时发现烤焦了要紧急停止一样,循环也有“紧急按钮”:
- `break`:立刻跳出整个循环
- `continue`:跳过本次,继续下一次循环
```php
";
break; // 完全停止;可以试试去掉这里,看看有什么不一样
}
echo "第" . $i . "个百吉饼完成!
";
}
// 当遇到break的时候,会跳出整个循环,于是就会输出以下内容
echo "全部完成!";
?>
```
最终会输出:
```
第1个百吉饼完成!
第2个百吉饼完成!
第4个百吉饼完成!
烤箱出故障了,停止!
全部完成!
```