PHP 教程:可运行示例边学边练
免费的 PHP 8 教程和参考:数组、foreach、字符串函数、日期、表单、类和错误处理。每个示例都能在页面上运行,输出 HTML 的示例会显示成页面,表单还可以提交。
开始课程入门
- PHP 是什么?PHP 是一种服务器端脚本语言:PHP 文件在 Web 服务器上运行,再把它生成的 HTML 发送给浏览器。WordPress、Laravel 和大量网站都由它驱动,它也能在命令行中运行脚本。
- 安装 PHP一条命令就能安装 PHP:Windows 用 winget,macOS 用 Homebrew,Linux 用 apt 或 dnf。然后在终端运行 php -v,检查 PHP 是否可用以及当前的版本。
- 运行 PHP在终端中用 php index.php 运行 PHP 文件,或者用 PHP 内置的服务器在浏览器中访问整个文件夹:php -S localhost:8000。两种方式都不需要 Apache 或 XAMPP。
- echo 和 printecho 输出一个或多个字符串:echo "Hello, $name"; 或 echo $a, $b;。它是向页面输出文本或 HTML 的常用方式。print 作用相同,但只接受一个值并返回 1;<?= $x ?> 是在 HTML 中使用的简写形式。
- 错误报告要显示所有 PHP 错误,在脚本顶部加上 ini_set('display_errors', '1'); 和 error_reporting(E_ALL);,或者在 php.ini 中设置 display_errors = On 和 error_reporting = E_ALL。在生产环境中,应把错误写入日志,而不是显示出来。
- PHP 与 PythonPHP 专为 Web 而生:每个请求运行一个文件并输出一个页面,WordPress 和 Laravel 都基于它。Python 是通用语言,也能做 Web 开发(Django、Flask),并在数据科学、自动化和机器学习领域占据主导。
- PHP 与 JavaScriptPHP 在服务器上运行,把生成好的 HTML 发送给浏览器。JavaScript 在浏览器中运行,在页面加载后修改页面,借助 Node.js 也能在服务器上运行。大多数网站两者都用:PHP 负责生成页面和 API,JavaScript 负责交互。
基础
- 变量PHP 变量以 $ 开头,给它赋值就创建了它:$name = "Ada";。PHP 没有声明关键字,也没有固定类型。函数看不到外部的变量,除非你把它传进去、使用 global 或捕获它;static 可以在多次调用之间保留值。
- 数据类型PHP 共有十种类型:四种标量类型(int、float、string、bool)、四种复合类型(array、object、callable、iterable)和两种特殊类型(null、resource)。var_dump($x) 显示值的类型和值,gettype($x) 返回类型名称。
- 字符串用点运算符拼接 PHP 字符串("Hello, " . $name),或者把变量直接写进双引号("Hello, $name")。单引号会原样输出文本。学习字符串拼接、.=、数组和对象的插值,以及用 strtoupper、strtolower、ucfirst 和 ucwords 转换大小写。
- Heredoc 和 NowdocPHP heredoc 是以 <<<EOT 开头、以 EOT 结尾的多行字符串,它会像双引号字符串一样替换变量。nowdoc(<<<'EOT')则原样保留文本。本文讲解语法、缩进规则、在其中插入数组和对象,以及 HTML 模板。
- 运算符PHP 运算符对值进行运算:+、-、*、/、%、** 用于算术,=、+=、.=、??= 用于赋值,&&、||、! 和 xor 用于逻辑,. 用于连接字符串,==、===、<=> 用于比较,++、-- 用于自增自减。本文用可运行示例逐组讲解,还有 and 与 && 的优先级陷阱。
- 比较运算符PHP 用 ==(类型转换后相等)、===(值相等且类型相同)、!= 和 !==、< > <= >= 以及太空船运算符 <=> 比较值。本文讲解 == 什么时候会转换类型、PHP 8 改了什么,并在一张表里列出所有松散比较和严格比较的结果。
- 三元运算符PHP 三元运算符就是一行写完的 if/else:$result = $condition ? 'yes' : 'no';。学习短三元 ?:(Elvis 运算符)、它和 ?? 的区别、为什么从 PHP 8 开始嵌套三元必须加括号,以及什么时候用 match 更清楚。
- 空合并运算符 ??PHP 空合并运算符返回它左侧的值,除非左侧为 null 或未设置:$name = $_GET['name'] ?? 'Guest';。遇到不存在的变量或数组键,它从不发出警告。学习 ?? 链式写法、??= 赋值、?? 与 ?: 的区别,以及空安全运算符 ?->。
- 类型转换用 (int) $str 或 intval($str) 把 PHP 字符串转成整数:(int) "42" 是 42,(int) "12abc" 是 12。学习转换为 int、float、bool、string 和 array,数字字符串,带进制的 intval,settype,以及如何校验输入,避免悄悄得到 0。
- 常量PHP 常量是一个值不能改变的名字:const MAX_USERS = 100; 或 define('MAX_USERS', 100);。本文讲解 define 与 const 的区别、常量数组、用 self:: 访问的类常量、用 defined() 检查常量,以及魔术常量 __LINE__、__FILE__、__DIR__ 和 __CLASS__。
- isset()变量存在且不为 null 时,isset($var) 返回 true,否则返回 false,而且不会产生任何警告。它也可以检查数组键和对象属性:isset($user['email'])。学习用 isset 检查多个变量、嵌套键,isset 与 empty、isset 与 array_key_exists 的区别,以及检查表单是否提交。
- empty()当变量不存在或为假值(null、false、0、0.0、""、"0" 或 [])时,empty($var) 返回 true。它从不对未定义的变量或键发出警告。了解为什么 "0" 也算空、如何精确判断空字符串或空数组、empty 与 isset 的区别,以及表单输入校验。
- 数学与取整round($x, 2) 保留两位小数,floor() 总是向下取整,ceil() 总是向上取整。另外还介绍 intdiv()、% 运算符、用于求幂的 **、abs()、max()、min(),以及生成随机数的 random_int()。
流程控制
- if、elseif、elseif (condition) { ... } 只在条件为真时执行代码块,elseif 检查下一个条件,所有条件都不满足时执行 else。本文讲解 elseif 和 else if 的区别、&& 和 ||、真值,以及 HTML 模板中的 if: endif; 语法。
- switch 语句switch ($value) 把一个值和一组 case 标签逐一比较,从第一个匹配的地方开始运行代码,直到遇到 break。内容包括多个 case 共用代码、贯穿、default、switch(true),以及为什么 switch 使用宽松的 == 比较。
- match 表达式match ($x) { 1, 2 => 'low', default => 'other' } 用严格的 === 比较一个值,返回第一个匹配分支的结果,没有任何分支匹配时抛出 UnhandledMatchError。内容包括 match 与 switch 的区别、match(true) 以及配合枚举使用 match。
- for 循环for ($i = 0; $i < 10; $i++) { ... } 把代码块运行固定的次数:初始值、每轮开始前检查的条件,以及步进。内容包括按索引遍历数组、倒数、嵌套循环、break 和 continue。
- while 循环while (condition) { ... } 只要条件为真就重复执行代码块,每轮开始前都检查一次条件。do { ... } while (condition); 在每轮结束后检查,所以至少会运行一次。内容包括用 break 退出 while (true)、逐行读取文件,以及如何避免死循环。
- foreach 循环foreach ($array as $value) 访问数组的每个元素,foreach ($array as $key => $value) 还能同时拿到键。内容包括获取索引、通过引用修改值以及 unset() 的陷阱、break 和 continue、嵌套数组、在 foreach 中使用 list(),以及生成 HTML。
数组
- 数组PHP 数组是一个有序的值列表,每个值都保存在一个键下:$fruits = ['apple', 'banana']; 用 $fruits[0] 读取。学习如何创建数组、读取和修改元素、遍历数组、输出数组,以及该用哪个数组函数。
- 关联数组PHP 关联数组把值存放在你命名的键下:$user = ['name' => 'Ada', 'age' => 36]; 用 $user['name'] 读取。本文讲解如何添加、修改和删除键值对,遍历键和值,获取所有键或值,检查键是否存在,以及按键或按值排序。
- 多维数组PHP 多维数组是元素本身也是数组的数组,每一层用一对方括号读取:$grid[1][2] 或 $users[0]['name']。学习如何创建嵌套数组、读取和修改深层的值、用嵌套 foreach 遍历、生成 HTML 表格、取出一列以及对行排序。
- array_push()array_push($arr, $value) 向数组末尾添加一个或多个值;只添加一个值时,$arr[] = $value 效果相同而且更快。学习如何在数组的末尾、开头或中间添加元素,添加键和值,并避开经典的返回值 bug。
- count()count($arr) 返回 PHP 数组中的元素个数,所以 count(['a', 'b', 'c']) 是 3。本文讲解如何用 COUNT_RECURSIVE 统计嵌套数组、sizeof() 和 count() 的关系、如何统计符合条件的值,以及为什么从 PHP 8 开始 count(null) 会抛出 TypeError。
- in_array()in_array($needle, $haystack) 在值存在于数组中时返回 true。第三个参数传 true 可以进行严格检查,in_array('1', $ids, true),这样类型也必须一致。本文讲解 strict 参数、不区分大小写的搜索、检查多个值、多维数组,以及更快的 isset 替代方案。
- array_search()array_search($value, $array) 返回第一个等于 $value 的元素的键,找不到时返回 false。因为键可能是 0,所以始终要用 !== false 检查结果。学习严格搜索、查找所有匹配项、用 array_column 搜索多维数组,以及不区分大小写的搜索。
- array_key_exists()array_key_exists('key', $array) 在数组中有这个键时返回 true,即使它的值是 null。isset($array['key']) 更快,但对 null 值会返回 false。学习两者的区别、key_exists()、嵌套键、同时检查多个键,以及为什么对象要用 property_exists()。
- array_merge()array_merge($a, $b) 把多个数组合并成一个:数字键的值会追加到末尾并重新编号,字符串键则以后面的数组为准。学习 array_merge 与 + 运算符、展开语法 [...$a, ...$b] 的区别,怎样合并一组数组,以及什么时候更适合用 array_merge_recursive 或 array_replace。
- array_map()array_map($callback, $array) 对每个元素调用回调函数,并返回一个由结果组成的新数组。学习箭头函数、键、同时映射多个数组,以及什么时候更适合用 array_walk 或 foreach。
- array_filter()array_filter($array, $callback) 保留回调函数返回 true 的元素。学习如何删除空值、用 ARRAY_FILTER_USE_KEY 按键过滤、用 array_values 重新索引结果,以及用 array_reduce 求和。
- 数组排序sort($array) 原地排序一个列表,asort 和 ksort 按值或按键给关联数组排序,usort 用你自己的比较函数排序。本文讲解所有这些函数,教你按某一列给多维数组排序,并避开经典的 $sorted = sort($arr) 错误。
- 删除数组元素unset($array[$key]) 按键删除元素,array_splice 按位置删除并重新编号,array_diff 按值删除。本文逐一讲解它们的用法、删除后如何重建索引,以及会删错元素的 array_search 陷阱。
- array_slice()array_slice($array, $offset, $length) 返回数组一部分的副本,不会修改原数组。学习用负数偏移量获取最后几项、preserve_keys、对列表分页,以及它和 array_splice 的区别。
- array_unique()array_unique($array) 删除重复的值,保留每个值第一次出现的元素及其原来的键。学习如何重新索引结果、不区分大小写地去重、对多维数组去重,以及统计或找出重复的值。
- array_column()array_column($rows, 'email') 会把每一行的 email 字段取出来,返回一个一维数组。学习索引键参数、按 id 重新索引行、在对象上使用它,以及配合 array_search 查找某一行。
- 解构和 list()[$a, $b] = $array 用一行代码把前两个元素分别赋值给 $a 和 $b,['name' => $name] = $user 则按键做同样的事。list() 是较旧的写法。本文讲解跳过元素、交换变量、返回多个值、在 foreach 中解构,以及 ... 展开运算符。
字符串
- explode()explode(',', $string) 在每个逗号处分割字符串,并以数组形式返回各个片段。内容包括 limit 参数、按换行符分割、去掉片段两端的空白,以及什么时候 str_split 或 preg_split 更合适。
- implode()implode(', ', $array) 把数组的值连接成一个字符串,并在值之间插入分隔符。本文讲解带键的 implode、关联数组、构建 HTML 列表,以及 implode 和 join 的区别。
- str_replace()str_replace($search, $replace, $subject) 把每一处 $search 都替换成 $replace。学习用数组同时替换多个值、统计替换次数、不区分大小写的 str_ireplace,以及只替换第一处匹配。
- substr()substr($string, $offset, $length) 返回字符串的一部分。学习负数偏移量、获取开头或末尾的字符、处理中文、日文等 UTF-8 文本的 mb_substr、substr_count,以及截断文本并加省略号。
- strpos()strpos($haystack, $needle) 返回 $needle 第一次出现的位置,找不到时返回 false。学习 !== false 判断、不区分大小写的 stripos、用 strrpos 找最后一处匹配,以及查找所有出现位置。
- str_contains()只要 $needle 出现在 $haystack 中的任意位置,str_contains($haystack, $needle) 就返回 true。学习不区分大小写的写法、str_starts_with 和 str_ends_with、检查多个关键词,以及 PHP 7 中的 strpos 写法。
- trim()trim($string) 去除字符串两端的空格、制表符和换行;ltrim() 和 rtrim() 只处理一端。学习怎么去除指定字符、全角空格,以及怎么处理数组中的每个值。
- strlen()strlen($string) 返回字符串的字节长度,mb_strlen($string) 返回字符长度。了解为什么中文、日文和 emoji 会得到不同的结果,怎么判断空字符串,以及怎么校验输入长度。
- sprintf()sprintf() 根据格式和值拼出一个字符串:sprintf('%05.2f', 3.14159) 返回 "03.14"。学习 %s、%d 和 %f,补零、固定小数位、对齐、参数编号,以及 printf 和 vsprintf。
- number_format()number_format(1234567.891, 2) 返回 "1,234,567.89":它按指定的小数位四舍五入,并加上千位分隔符。本文介绍它的四个参数、欧洲和日本的数字格式、它的舍入方式,以及为什么返回的是字符串。
- 换行在 PHP 中输出换行,可以在双引号中写 "\n",或者使用 PHP_EOL。在网页中换行符是看不见的,所以要用 <br> 或 nl2br()。了解它们的区别、为什么单引号中的 '\n' 不起作用,以及如何按行拆分或去掉换行符。
- preg_match()preg_match($pattern, $subject, $matches) 用正则表达式检测字符串,匹配时返回 1,不匹配时返回 0。本文讲解捕获组、命名组、preg_match_all、锚点、校验用的正则,以及处理 UTF-8、中文和日文文本的 u 修饰符。
- preg_replace()preg_replace($pattern, $replacement, $subject) 替换正则表达式的每一处匹配:preg_replace('/\s+/', ' ', $text) 会合并连续的空白。本文讲解 $1 这样的反向引用、preg_replace_callback、删除非字母数字字符、替换次数限制和计数。
函数
- 函数用 function name($param) { return $value; } 定义 PHP 函数。本文讲解返回值、默认参数、命名参数、引用传递、strict_types 类型声明、可空类型和联合类型,以及可变参数 ...$args。
- 箭头函数箭头函数是只含一个表达式的匿名函数,写作 fn($x) => $x * 2。它会自动按值使用外部变量,不需要 use。本文讲解它的语法、变量捕获的方式、类型声明、作为 array_map 和 usort 的回调,以及什么时候应该改用完整的闭包。
- 匿名函数匿名函数(闭包)是保存在变量中的没有名字的函数:$greet = function ($name) { return "Hi $name"; };。学习 use 关键字、按引用捕获、callable、一等可调用语法 strlen(...),以及类中的闭包。
- 生成器生成器是用 yield 逐个返回值的函数,foreach 可以遍历一百万个元素而无需先构建数组。本文讲解 yield、键、节省内存、无限序列、yield from、send() 和 getReturn()。
类与对象
- 类和对象PHP 类是用 `class Name { }` 声明的蓝图,`new Name()` 用它创建对象。本文讲解属性、方法、`$this`、public/private/protected、类型化属性和只读属性、`__toString`,以及为什么赋值一个对象并不会复制它。
- 构造函数PHP 构造函数就是 `__construct()` 方法,在你写 `new ClassName(...)` 时自动运行。本文讲解构造函数属性提升、默认参数和命名参数、参数校验、`parent::__construct()`,以及如何用静态工厂方法模拟多个构造函数。
- 继承在 PHP 中,一个类用 `class Dog extends Animal` 继承另一个类:它获得父类的 public 和 protected 属性与方法,可以重写它们,也仍然可以用 `parent::method()` 调用原来的方法。本文讲解方法重写、protected、final、instanceof 以及 private 方法的陷阱。
- 接口PHP 接口列出一个类承诺要有的方法:`interface Shape { public function area(): float; }`,之后 `class Circle implements Shape` 就必须定义 `area()`。学习用接口做类型声明、实现多个接口、接口常量、接口继承接口、Countable 等内置接口,以及接口与抽象类的区别。
- 抽象类PHP 抽象类用 `abstract class Name` 声明,不能被实例化:它的存在就是为了被继承。它可以包含属性、构造函数和完整的方法,还可以包含没有方法体的 `abstract` 方法,每个子类都必须实现这些方法。
- traitPHP trait 是一段可复用的方法和属性,用 `use TraitName;` 把它复制进类里。学习使用多个 trait、用 `insteadof` 和 `as` 解决命名冲突、trait 中的抽象成员和静态成员、哪个方法优先,以及 trait、接口和抽象类的区别。
- 枚举PHP 枚举(8.1+)是只有固定几个取值的类型:`enum Status: string { case Active = 'active'; }`。内容包括纯枚举和 backed 枚举、`->value` 与 `->name`、用 `from()` 和 `tryFrom()` 转换、用 `cases()` 列出所有 case、方法、常量、接口,以及在 `match` 中使用枚举。
- 静态方法`static` 方法或属性属于类,而不属于某个对象:用 `ClassName::method()` 调用,用 `ClassName::$count` 读取,不需要 `new`。学习 `self::` 和 `static::` 的区别(后期静态绑定)、`new static`、函数内的静态变量,以及什么时候不该用 static。
- 命名空间PHP 命名空间把类、函数和常量归到一个前缀之下:在文件顶部写 `namespace App\Models;`,然后在其他地方写 `use App\Models\User;`,就能用短名称引用这个类。学习完全限定名称、`use ... as` 别名、`use function`、全局类前面的反斜杠,以及 PSR-4 自动加载。
错误与异常
- try catch在 PHP 中,可能出错的代码放进 `try { }` 代码块,一旦抛出异常就运行 `catch (Exception $e) { }`,原因在 `$e->getMessage()` 里。学习捕获多种异常类型、`finally`、为什么 `catch (Exception)` 捕获不到 `TypeError` 和 `DivisionByZeroError`(要捕获 `Throwable`)、重新抛出,以及全局处理函数。
- 异常在 PHP 中用 `throw new Exception('message');` 抛出异常,调用方在 `catch` 块中用 `$e->getMessage()` 读取信息。内容包括内置异常类、编写自定义异常、`getCode()`、`getLine()` 和 `getPrevious()`、作为表达式的 `throw`,以及 PHP 自身抛出的 `ValueError`、`TypeError` 等错误。
数据、文件与 Web
- json_encode()json_encode($value) 把 PHP 数组、对象或标量转换为 JSON 字符串。关联数组变成 JSON 对象,列表变成 JSON 数组,JSON_PRETTY_PRINT 和 JSON_UNESCAPED_UNICODE 等标志用来控制输出。
- json_decode()json_decode($json, true) 把 JSON 字符串转换为 PHP 关联数组;不传 true 时得到的是 stdClass 对象。学习读取嵌套的值、用 JSON_THROW_ON_ERROR 处理错误、json_validate(),以及 json_decode 为什么返回 null。
- date()date('Y-m-d') 以字符串形式返回今天的日期,date('Y-m-d', $timestamp) 可以格式化任意 Unix 时间戳。本文通过可运行的示例讲解 time()、mktime()、strtotime()、DateTimeImmutable、日期加天数、两个日期的差值以及时区。
- 日期格式PHP 日期格式是由字母组成的字符串,比如 'Y-m-d H:i:s',其中 Y 是 4 位数的年份,m 是月,d 是日,H 是小时,i 是分钟,s 是秒。本文列出每个格式字符及其示例,还讲解了转义、ISO 8601、日文日期,以及如何把一种格式转换成另一种。
- file_get_contents()file_get_contents('notes.txt') 读取整个文件并以一个字符串返回,读取失败时返回 false。与它配套的 file_put_contents() 把字符串写入文件,file() 则以数组形式返回文件的各行。
- fopen()fopen('data.txt', 'r') 打开一个文件并返回文件句柄,失败时返回 false。用 fgets() 或 fread() 从句柄读取,用 fwrite() 写入,用 fclose() 关闭。模式('r'、'w'、'a'、'x'、'c' 及其带 + 的形式)决定了你能做什么。
- include 和 requireinclude 'header.php'; 会在当前位置运行另一个 PHP 文件,就像把它的代码粘贴到这里一样。require 的作用相同,但文件不存在时会以致命错误终止脚本,而 include 只发出警告并继续执行。include_once 和 require_once 只在第一次时加载文件。
- 表单PHP 表单用 <form method="post"> 提交给自己,当 $_SERVER['REQUEST_METHOD'] 为 'POST' 时,脚本从 $_POST['name'] 读取字段。本文讲解表单验证、在字段旁显示错误信息、保留已填写的值、复选框和下拉框,以及输出转义。
- $_GET 和 $_POST$_GET 保存 URL 的查询字符串(?page=2 对应 $_GET['page']),$_POST 保存用 method="post" 提交的表单字段。本文讲解如何安全地读取两者、用 http_build_query 构建链接、检查 $_SERVER['REQUEST_METHOD'],以及如何在 GET 和 POST 之间选择。
- htmlspecialchars()htmlspecialchars($text) 会把 HTML 中有特殊含义的五个字符(& < > " ')转换成实体,让用户输入显示为文本,而不会被当作标记执行。本文讲解 ENT_QUOTES、转义属性、重复编码、htmlentities 和 strip_tags。
- Session 和 Cookie在每个页面的开头调用 session_start(),然后把值存进 $_SESSION['key'];PHP 把它们保存在服务器上,并通过一个 cookie 认出访问者。setcookie('name', 'value', $options) 在浏览器中保存一个小值,之后从 $_COOKIE 读回。本文讲解登录、退出登录、闪存消息、cookie 选项和安全设置。
- password_hash()用 password_hash($password, PASSWORD_DEFAULT) 对密码做哈希,保存这个 60 个字符的结果,登录时用 password_verify($password, $hash) 校验。本文讲解为什么每次的哈希都不同、cost、password_needs_rehash、Argon2id,以及为什么不能用 md5 和 sha1 存密码。
- filter_var()filter_var($value, FILTER_VALIDATE_EMAIL) 在值有效时返回该值,无效时返回 false;FILTER_VALIDATE_INT、FILTER_VALIDATE_URL、FILTER_VALIDATE_BOOL 和 FILTER_VALIDATE_IP 的用法相同。内容包括选项(min_range、default)、标志、净化过滤器,以及 false 与 0 的陷阱。
- PDOPDO 是 PHP 的数据库访问层:用 new PDO('mysql:host=localhost;dbname=shop;charset=utf8mb4', $user, $pass) 建立连接,然后所有带值的查询都通过 prepare() 和 execute([$value]) 执行。本文讲解连接、预处理语句、获取行、插入、事务和 WHERE IN。
- ComposerComposer 是 PHP 的包管理器:composer require vendor/package 把库下载到 vendor/,记录到 composer.json 中,再用 require 'vendor/autoload.php' 加载它。本文讲解如何安装 Composer、composer.json、版本约束、install 与 update 的区别,以及为自己的类配置 PSR-4 自动加载。