拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PHP基础语法详解:环境搭建、变量函数与注册验证实战

PHP基础语法详解:环境搭建、变量函数与注册验证实战

1. 先把话说明白:学 PHP 语法前要准备的几个认知

如果你是因为期末要交 PHP 大作业、或者想做个带后台的小网站才点进来看这篇“PHP 基础案例教程之 02-PHP 语法基础”,那恭喜你,找对地方了。这节内容不整那些虚头八脑的概念,就讲 PHP 语法里最核心、最常用的那部分:变量、数据类型、运算符、流程控制、函数、数组、字符串处理,再配合三个能直接拿去改的案例。学完你至少能看懂大部分入门源码,也能自己写点简单的业务逻辑。

先回答一个很多人纠结的问题:PHP 语法到底难不难?我的答案是,PHP 的语法门槛比 Java 和 C++ 低一大截,但它有自己反直觉的地方,比如$符号、弱类型、数组和字符串函数多到记不住。如果你之前学过 Python 或 JavaScript 的基础语法,再来看 PHP,很多概念是相通的,但细节差异非常坑人。

学语法最忌讳的就是“啃字典”。我见过太多人把 PHP 手册从头翻到尾,结果真要写注册验证消息代码时照样卡壳。正确做法是带着目标去学:比如你想做一个带登录注册的系统,那你就得掌握变量怎么存数据、if怎么判断、$_POST怎么接收表单、header怎么跳转,这些语法点一旦和场景绑定,记起来就特别快。

这节教程基于一台 Windows 电脑、一个 phpstudy 或者本地 PHP 环境来演示。你不需要理解底层原理,跟着敲就行。但有一点我要提前说清楚:PHP 是一门“服务端脚本语言”,代码是跑在服务器上的,不是双击.php文件就能在浏览器里看到结果。所以第一步不是写代码,而是把环境搭对。

2. 环境选型与第一个脚本:跑不通代码一切都白搭

2.1 phpstudy 换 PHP 版本时最容易踩的坑

很多新手的第一个卡点不是语法,而是环境。国内用 phpstudy 的人最多,它把 Apache/Nginx、MySQL、PHP 集成在一个面板里,点两下就能用。但 phpstudy 默认带的 PHP 版本可能比较旧,比如 5.6 或 7.0,而你在网上找的源码要求 7.4 以上。这时候就需要升级 PHP 版本。

具体操作:打开 phpstudy 面板,切到“软件管理”或“PHP 管理”,下载想要的版本,然后在“站点”配置里把 PHP 版本切过去即可。但这里有个隐蔽问题:切换版本后,原来 php.ini 里的扩展配置可能不生效。比如你开了extension=mysqli,换版本后你会发现连数据库报“未定义函数 mysqli_connect”,因为新版本的扩展目录路径变了,你要重新在 php.ini 里确认扩展路径指向新版本的ext目录。

如果你不想用集成环境,另一个轻量方案是用 PHP 内置开发服务器。在命令行里进入项目目录,执行:

php -S localhost:8080

这样就能在浏览器访问http://localhost:8080/你的脚本.php。我目前用 VSCode 写 PHP 代码,配一个 Code Runner 插件,或者直接在终端跑php 文件名.php做纯语法验证,效率很高。为什么推荐命令行?因为你会很快遇到语法错误,而命令行会把错误信息直接打在终端里,比浏览器“白屏”直观得多。

2.2 第一个脚本:从 Hello World 看 PHP 标签和注释

PHP 文件通常以<?php开头,以?>结尾(结尾标签可以省略,省略更好,能避免意外输出空白字符)。先写一个最基础的:

<?php // 这是单行注释 # 这也是单行注释 /* 这是多行注释 */ echo "Hello World";

echo是最常用的输出语句,它不是一个函数,而是一种语言结构,所以可以不加括号直接写。这里有个细节:PHP 代码里每行语句结尾要加分号;,漏了分号就会报语法错误。我第一次教朋友写 PHP,他连续三次漏分号,报错信息都是“syntax error, unexpected end of file”,他以为代码写错了,其实就是少了个;。

还有一点新手经常迷惑:为什么 PHP 代码能直接嵌在 HTML 里?比如:

<!DOCTYPE html> <html> <body> <h1><?php echo "标题"; ?></h1> </body> </html>

这是因为 PHP 引擎把文件里<?php ... ?>之外的部分原样输出,只有标签内的部分会被解析执行。这也是 PHP 作为 Web 语言的巨大优势:你可以像写 HTML 一样写页面,在需要动态数据的地方插入 PHP 小段。理解了这一点,再看“html 与 PHP 注册后验证消息代码”这类需求就不会慌——无非是页面用 HTML 排版、PHP 做数据处理和消息输出。

3. 变量、常量与数据类型:让数据在代码里有个“家”

3.1 变量的命名规则和“可变变量”的迷惑行为

PHP 的变量用$加名字表示,比如$name、$age。命名规则是:以字母或下划线开头,后面跟字母、数字、下划线,区分大小写。$Name和$name是两个不同的变量。

这里有个细节值得多说一句:PHP 变量默认是“传值赋值”,也就是说:

$a = 10; $b = $a; $b = 20; echo $a; // 输出 10

$b = $a是把$a的值复制一份给$b,两者互不影响。但如果你写成$b = &$a;,这就成了引用赋值,$b和$a指向同一块内存,改一个另一个也变。这个概念做大型项目时特别重要,尤其是数组拷贝和函数参数传递,搞不清引用关系很容易写出隐蔽 bug。

PHP 还有个“可变变量”写法:

$name = 'hello'; $$name = 'world'; // 相当于 $hello = 'world' echo $hello; // world

这个功能很少用,但很多面试题爱考。我的建议是:知道有这个东西就行,实际业务里别用,因为可读性太差,你同事会骂人的。

3.2 数据类型:弱类型带来的便利与陷阱

PHP 有八种基础数据类型:四种标量类型(布尔bool、整型int、浮点型float、字符串string),两种复合类型(数组array、对象object),两种特殊类型(资源resource、null)。

PHP 是弱类型语言,变量不用声明类型,赋值是什么就是什么:

$var = "123"; // 字符串 $var = 123; // 变成整型 $var = 1.5; // 变成浮点

这很方便,但也埋了很多雷。最典型的是比较运算:

var_dump(0 == "abc"); // bool(false) 但要注意 PHP 8 之前的结果是 true

在 PHP 7 及更早版本,0 == "abc"会返回true,因为字符串被转换成数字 0。PHP 8 修复了这个问题。如果你维护老项目,这种“魔法比较”能让你排查到怀疑人生。所以我给新手的铁律是:比较时能用===(全等)就不要用==(等于),===同时比较值和类型,不会做自动转换。

PHP 8 之后还引入了联合类型、构造函数属性提升等新特性,但基础语法层面你先不用关心,后面进阶再说。

3.3 常量:用 define 还是 const?

常量是一旦定义就不能改变的值。定义方式有两种:

define('SITE_NAME', '我的网站'); const PI = 3.14;

区别在于:const是在编译期定义的,必须写在顶层作用域(不能放在 if 里),效率略高;define是运行期定义的,可以放在任何地方,还支持定义数组(PHP 7 后const也支持定义常量数组了)。我的习惯是,全局配置用const,需要条件判断才用define。

再记一个魔术常量:__LINE__返回当前行号,__FILE__返回当前文件路径,__DIR__返回当前文件所在目录。调试时很有用,我经常用echo __LINE__;来定位代码执行到了哪里。

4. 运算符与表达式:最容易被小看的语法陷阱

4.1 算术运算符和字符串拼接:点号不是加号

PHP 的算术运算符有+ - * / %(加减乘除取余),这里两个坑:

第一,/的除法结果如果除不尽,会返回浮点数,比如echo 5 / 2;输出2.5。而在 C 语言里5 / 2会得到 2,PHP 不做整数截断。这其实更符合直觉,但有时候你希望取整,可以用intdiv(5, 2)或者(int)(5 / 2)。

第二,字符串拼接用的是点号.,不是加号:

$firstName = "张"; $lastName = "三"; echo $firstName . $lastName; // 张三 echo $firstName + $lastName; // 0,因为字符串被转成数字相加

第二个写法会输出 0,因为+是算术加号,PHP 会把两边的非数字字符串转换成 0 再相加。这是新手写 PHP 最容易犯的错误之一,我之前看一个学弟的代码,页面上一堆 0,就是他把.全写成+了。

4.2 比较运算符和逻辑运算符:写条件判断前先想清楚

比较运算符除了==和===,还有!=(不等)和!==(不全等)、>、<、>=、<=、<=>(太空船运算符)。

<=>是 PHP 7 新增的,$a <=> $b在$a大于$b时返回 1,相等返回 0,小于返回 -1。排序算法里特别好用,比如按分数排序时一句回调函数就能搞定。

逻辑运算符有三种写法:and、or、not,以及符号写法&&、||、!。它们优先级不同,&&比and高。新手经常在这上面翻车:

$result = true and false; var_dump($result); // true,因为 and 优先级低,实际执行的是 ($result = true) and false

如果你希望$result等于true && false的结果,必须写成$result = (true and false);或者直接用&&。我给个建议:业务代码里统一用&&、||、!,不要用单词写法,少一个雷。

4.3 三元运算符和空合并运算符:让代码变简洁的利器

三元运算符条件 ? 值1 : 值2是 if-else 的简写。PHP 7 还多了个??(空合并运算符),它等同于“判断一个变量是否存在且不为 null”:

$username = $_POST['username'] ?? '游客';

这行代码的意思是:如果$_POST['username']存在且不是 null,就取它的值,否则取'游客'。在 PHP 7 之前你得像这样写:

$username = isset($_POST['username']) ? $_POST['username'] : '游客';

显然??简洁很多。还有一个?:运算符,$a ?: $b表示如果$a为真就取$a,否则取$b。注意?:判断的是“真值”,0、空字符串、null 都算假;而??只判断“是否存在且不为 null”。这两个别搞混。

4.4 错误抑制符 @:看着省事,实际上是遮羞布

@可以放在表达式前面,抑制该表达式产生的错误信息:

$content = @file_get_contents('http://example.com');

如果请求失败,正常会报一个 warning,加了@就什么也不显示。问题是,@只抑制显示,不解决根本问题。你要是运气不好,还在 PHP 8 之前遇到@作用于未定义变量时依然会有一个空错误的情况,排查起来更麻烦。

有些人还误把@当“安全措施”,觉得加了它就不会暴露错误。实际上生产环境里你应该做的是设置error_reporting和display_errors,把错误记到日志里,而不是用@捂住。我自己的项目里基本不用@,因为一个 warning 往往就是 bug 的先兆,遮掉等于埋雷。

5. 流程控制与函数:写业务逻辑的基本功

5.1 if、else 和 switch:别把条件写复杂了

流程控制是业务逻辑的骨架。最常见的if/elseif/else:

$score = 85; if ($score >= 90) { echo "优秀"; } elseif ($score >= 60) { echo "及格"; } else { echo "不及格"; }

注意elseif是一个单词,没有空格。PHP 还支持else if分开写,两者等价,但会触发一些小风格问题,统一用elseif就好。

当分支很多时,switch比一长串elseif更清晰:

switch ($grade) { case 'A': echo "优秀"; break; case 'B': echo "良好"; break; default: echo "未知等级"; }

switch用的是松散比较==,也就是会做类型转换。如果你要严格比较类型,PHP 8 之后有match表达式,它用的是===严格比较,而且不需要写break,还返回一个值:

$result = match ($grade) { 'A' => "优秀", 'B' => "良好", default => "未知", };

这是一个 PHP 8 的语法亮点,专门针对switch容易忘记break的痛点设计的。老项目里可能还用不上,但新项目我建议优先match。

5.2 循环:for 循环和 foreach 的使用边界

PHP 的循环有for、while、do-while、foreach。其中foreach是遍历数组的灵魂。

$fruits = ['apple', 'banana', 'orange']; foreach ($fruits as $fruit) { echo $fruit . '<br>'; }

foreach还有带键名的写法:

$user = ['name' => '张三', 'age' => 20]; foreach ($user as $key => $value) { echo $key . ':' . $value . '<br>'; }

这里有个很重要的点:foreach遍历的是数组的副本,不是原数组。如果你在循环里直接修改$fruit,原数组不会变。想修改原数组,可以加引用:

foreach ($fruits as &$fruit) { $fruit = strtoupper($fruit); } unset($fruit); // 记得销毁引用变量

最后一行unset($fruit)特别关键。如果不销毁,后面代码里$fruit仍然指向数组最后一个元素,再操作它就是改数组里的值了,这种 bug 非常隐蔽。我踩过一次,排查了三个小时才发现是 foreach 引用没 unset 导致的。

while循环适合不知道循环次数的场景,比如读取文件行:

while (($line = fgets($fileHandle)) !== false) { echo $line; }

5.3 函数:参数默认值、返回值类型和局部作用域

函数用function关键字定义:

function add($a, $b) { return $a + $b; } echo add(2, 3); // 5

给参数一个默认值:

function greet($name = "世界") { return "你好," . $name; }

PHP 7 开始支持标量类型声明,PHP 8 还支持联合类型。我们可以这么写:

function add(int $a, int $b): int { return $a + $b; }

如果传入字符串"2",因为开启了严格模式的话会报类型错误;非严格模式下会尝试自动转换。对于基础语法学习,我建议从第一天起就养成写类型声明的习惯,函数更自文档化,也能提前拦截一部分传参错误。

函数内的变量默认是局部变量,函数外同名变量的值进不来:

$name = "外部"; function test() { echo $name; // 报错或输出 null }

如果你非要访问全局变量,要用global关键字,或者直接用超全局变量$GLOBALS:

$name = "外部"; function test() { echo $GLOBALS['name']; }

能不用global就别用,函数依赖外部状态会让代码极难维护。更优方案是把值作为参数传进来,把结果 return 出去,这是函数式思维的基础,PHP 里同样适用。

6. 字符串与数组:处理数据的两把刷子

6.1 字符串函数:从拆红包到金额大写都能用它

PHP 字符串函数多到令人发指,但真正高频的就那些:strlen(长度)、str_replace(替换)、substr(截取)、explode(分割成数组)、trim(去空格)、strpos(查找位置)、strtoupper/strtolower(大小写转换)。

从热搜词里有个“php 把小写的数字金额转为大写”,这就是一个标准的字符串处理案例。核心思路:把金额拆成整数和小数部分,逐个拆成单个数字,再用映射关系转换成大写数字:

function moneyToChinese($num) { $digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']; $units = ['', '拾', '佰', '仟']; $bigUnits = ['', '万', '亿']; // 拆成整数和小数 $parts = explode('.', (string)$num); $intPart = intval($parts[0]); $decPart = isset($parts[1]) ? substr($parts[1], 0, 2) : ''; // 处理小数角分 $decStr = ''; if ($decPart !== '') { $jiao = isset($decPart[0]) ? intval($decPart[0]) : 0; $fen = isset($decPart[1]) ? intval($decPart[1]) : 0; if ($jiao > 0) { $decStr .= $digits[$jiao] . '角'; } elseif ($fen > 0) { $decStr .= '零'; } if ($fen > 0) { $decStr .= $digits[$fen] . '分'; } } // 简化版整数部分处理 $intStr = ''; $numStr = (string)$intPart; $len = strlen($numStr); for ($i = 0; $i < $len; $i++) { $digit = intval($numStr[$i]); $unitPos = $len - $i - 1; $unitIndex = $unitPos % 4; $bigIndex = intdiv($unitPos, 4); $intStr .= $digits[$digit] . $units[$unitIndex]; // 每当遇到千位,追加万或亿 if ($unitIndex === 0 && $unitPos > 0) { $intStr .= $bigUnits[$bigIndex]; } } return ($intStr === '' ? '零' : $intStr) . '元' . ($decStr === '' ? '整' : $decStr); } echo moneyToChinese(12345.67); // 壹万贰仟叁佰肆拾伍元陆角柒分

这个版本是简化处理,大量金额转换需求还要补“零”的规则、连续的零合并等,但已经能体现字符串函数组合使用的思路了。我写这类函数时习惯先把需求拆成“整数部分”“小数部分”两个子问题,再逐个实现,比一口气写完好调试得多。

6.2 数组:从热搜词“["1","2"]怎么取出数字”说起

数组是 PHP 最强大的数据结构。可以分为索引数组和关联数组:

$list = [10, 20, 30]; // 索引数组 $user = ['name' => '张三', 'age' => 20]; // 关联数组

热搜词里有个“php 类,?["1","2′"]怎么取出数字 php”,这其实说的是 JSON 解析。前端传过来一个 JSON 字符串["1","2"],PHP 端怎么拿到里面的数字?

$json = '["1","2"]'; $arr = json_decode($json, true); // 第二个参数 true 表示转成数组,不写则变成对象 foreach ($arr as $value) { echo intval($value) . '<br>'; }

json_decode是处理接口数据的关键函数。这里有两个细节:第一,json_decode默认把 JSON 对象转成 PHP 对象,加上true才转成关联数组——很多新手不知道这个,取出$arr['name']时报错“Cannot use object as array”,其实把第二个参数设为true就行;第二,JSON 中文编码问题,json_encode默认会把中文转成\uXXXX格式,想要中文原样输出,要加 JSON_UNESCAPED_UNICODE 选项。

热搜词里还有“php 接口数组对象”,讲的就是 API 返回数据时,数组/对象与 JSON 之间的互相转换。Web 服务端返回数据,典型写法:

$data = [ 'code' => 0, 'msg' => 'success', 'data' => [ 'list' => $list, 'total' => count($list) ] ]; echo json_encode($data, JSON_UNESCAPED_UNICODE);

这一小段几乎就是后端接口开发的基础模板。

6.3 数组常用函数:排序、过滤、映射一把梭

处理数组的常用内建函数:

  • count()统计元素数量
  • array_merge()合并一个或多个数组
  • array_pop()/array_push()从尾部取出/压入元素
  • array_shift()/array_unshift()从头部取出/压入元素
  • in_array()检查值是否存在
  • array_map()对每个元素应用回调函数
  • array_filter()过滤元素
  • sort()/rsort()排序

一个实际场景:从用户表里取出所有年龄大于 18 的人名。

$users = [ ['name' => '张三', 'age' => 20], ['name' => '李四', 'age' => 17], ['name' => '王五', 'age' => 22], ]; $adults = array_filter($users, function ($user) { return $user['age'] >= 18; }); foreach ($adults as $user) { echo $user['name'] . '<br>'; }

用array_filter加匿名函数(闭包),比手写循环简洁很多。PHP 的闭包语法就是function ($参数) use ($外部变量) { ... },用use把外部变量带进来,这是 PHP 比较有特色的地方。不过新手阶段,你先会用普通函数,闭包下次再深入也不迟。

7. 错误处理与调试技巧:排错比写码更见功力

7.1 错误级别:哪些错误能忽略,哪些必须修

PHP 的错误级别用 E_ 开头的常量表示:E_NOTICE(提示)、E_WARNING(警告)、E_ERROR(致命错误)。不致命的不影响执行,但致命错误会直接中断脚本。

开发环境建议把所有错误都显示出来:

error_reporting(E_ALL); ini_set('display_errors', '1');

生产环境则相反,要隐藏错误显示、记录日志:

ini_set('display_errors', '0'); ini_set('log_errors', '1');

搜索引擎热搜里反复出现“php 错误处理”,说明这确实是很多人的痛点。实际上 90% 的 PHP 报错无非四类:语法错误(漏分号、括号不匹配)、未定义变量/数组键、调用未定义函数、文件包含路径错误。你把错误信息完整贴到搜索引擎里,基本都能找到答案,关键是先学会看错误信息。

7.2 异常处理:try-catch 别只在“感觉会出错”的地方用

PHP 5 之后有了完整的异常机制:

try { $file = fopen('xxx.txt', 'r'); if (!$file) { throw new Exception('文件打开失败'); } } catch (Exception $e) { echo '捕获异常:' . $e->getMessage(); } finally { echo '不管是否异常都会执行'; }

很多人误以为try-catch能捕获所有错误,这是个误区。PHP 里“错误”和“异常”是两回事:Error和Exception都是Throwable的子类,但老代码里很多函数失败时只发 warning,不抛异常。所以你不能指望try-catch包住一切,实际项目里通常要配合错误处理器来兜底。

PHP 8 之后,大部分致命错误变成了可捕获的Error异常,比如TypeError、ValueError。你可以在入口文件注册一个全局异常处理器:

set_exception_handler(function ($e) { error_log($e->getMessage()); echo '服务器内部错误'; });

这个做法的好处是用户不会看到一堆看不懂的堆栈信息,而错误细节已经进日志了。

7.3 在浏览器控制台里输出 PHP 变量

热搜词里有“怎么用 PHP 在浏览器控制台输出变量”。这个需求很实际,echo var_dump($var)打印在页面上,既影响排版又不够明显。一个轻量技巧是借助 JavaScript 的console.log:

function consoleLog($data) { $json = json_encode($data, JSON_UNESCAPED_UNICODE); echo "<script>console.log(" . $json . ");</script>"; } $array = ['name' => '张三', 'age' => 20]; consoleLog($array);

浏览器按 F12 打开控制台,就能看到格式化输出的变量,调试 AJAX 接口、排查数据传递问题时特别好用。调试完记得删掉,不然控制台一堆日志,真出问题时反而干扰判断。

7.4 var_dump 和 print_r 的区别

新手最常用的调试函数是var_dump和print_r。区别在于:var_dump会显示变量类型和长度,输出更详细;print_r只显示结构,可读性更好一点。排查类型不匹配的问题,必用var_dump,因为你看一眼括号里的string(3)就知道值是字符串还是数字。

调试数组嵌套结构时我推荐加个pre标签美化:

echo '<pre>'; print_r($users); echo '</pre>';

浏览器会把数组结构格式化换行显示,一眼看穿层级关系。

8. 从语法到实战:三个拿来就能用的基础案例

8.1 案例一:HTML 注册页面与 PHP 提示消息联动

这是热搜词“html 与 PHP 注册后验证消息代码”对应的场景。核心逻辑是:前端收集用户名和密码,提交到后端,后端校验后返回提示信息。

注册页register.php写在一个文件里同时处理显示和提交:

<?php $error = ''; $success = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $username = trim($_POST['username'] ?? ''); $password = $_POST['password'] ?? ''; $confirm = $_POST['confirm'] ?? ''; if ($username === '') { $error = '用户名不能为空'; } elseif (strlen($username) < 3 || strlen($username) > 20) { $error = '用户名长度需在 3 到 20 个字符之间'; } elseif (strlen($password) < 6) { $error = '密码至少 6 位'; } elseif ($password !== $confirm) { $error = '两次输入的密码不一致'; } else { // 密码加密 $passwordHash = password_hash($password, PASSWORD_DEFAULT); // 这里可以连接数据库,把 $username 和 $passwordHash 存进去 $success = '注册成功,欢迎 ' . $username; } } ?> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>注册</title> </head> <body> <?php if ($error !== '') { ?> <div style="color: red;"><?php echo $error; ?></div> <?php } ?> <?php if ($success !== '') { ?> <div style="color: green;"><?php echo $success; ?></div> <?php } ?> <form method="post"> <label>用户名:<input type="text" name="username" value="<?php echo htmlspecialchars($_POST['username'] ?? ''); ?>"></label><br> <label>密码:<input type="password" name="password"></label><br> <label>确认密码:<input type="password" name="confirm"></label><br> <button type="submit">注册</button> </form> </body> </html>

几个语法点供复习:$_SERVER['REQUEST_METHOD']获取请求方式,$_POST接收表单数据,??空合并运算符防未定义变量告警,htmlspecialchars防止 XSS,password_hash做密码哈希——这已经超出基础语法,但用到“函数调用”这个核心概念。你把这个案例跑通,PHP 语法的基础框架基本就串起来了。

8.2 案例二:foreach 加字符串操作模拟简单的批量处理

热搜词“excel 批量处理 php”,其实核心就是循环加字符串处理。比如从一行姓名:张三,年龄:20的数据里解析出结构:

$rawList = [ '姓名:张三,年龄:20', '姓名:李四,年龄:17', '姓名:王五,年龄:22', ]; $result = []; foreach ($rawList as $line) { $parts = explode(',', $line); $item = []; foreach ($parts as $part) { [$key, $value] = explode(':', $part); $item[$key] = $value; } $result[] = $item; } foreach ($result as $user) { echo "姓名:{$user['姓名']},年龄:{$user['年龄']}<br>"; }

这里有个很值钱的语法点:字符串里的变量插值。双引号字符串可以直接写"{$user['姓名']}",把变量解析进字符串里。但要注意:数组取值时花括号不能省,否则 PHP 会把$user后面一截当字符串处理,报错或者输出错误内容。这个写法看着花哨,其实非常常用,尤其是拼 SQL、拼 HTML 时能省不少连接符。

8.3 案例三:PHP 伪协议和文件包含的安全意识

热搜词出现了“php 伪协议”“php 文件包含漏洞”。这已经超出基础语法,但它和语法本身有很大关系。所谓伪协议,是指 PHP 支持的一些特殊流封装器,比如php://input、php://filter。举例:

echo file_get_contents('php://input');

这能读取请求体原始内容。再比如php://filter/read=convert.base64-encode/resource=test.php可以读取文件并返回 base64 编码内容。

在文件包含函数中,如果开发者直接拼接用户输入做包含:

$page = $_GET['page']; include($page . '.php');

攻击者传入php://filter/convert.base64-encode/resource=config,就能把config.php源码读出来。这类问题的根子是对“用户输入不可信”没有敬畏。学语法阶段你不一定要深入研究漏洞利用,但至少要在写代码时养成两个习惯:第一,文件路径不要直接由用户拼接;第二,对用户输入做白名单校验。

同类话题还有“php 反序列化漏洞”,和 PHP 的serialize/unserialize函数有关:

$data = ['name' => '张三']; $serialized = serialize($data); echo $serialized; // a:1:{s:4:"name";s:6:"张三";} $restored = unserialize($serialized); print_r($restored);

序列化把数组转成字符串存储,反序列化再还原。一旦你要反序列化的字符串包含用户输入,而那个类里有魔术方法(__wakeup、__destruct等),就可能被利用触发危险操作。热搜里的“php 序列化中文”说的则是中文字符序列化后长度计算的问题,s:6:"张三"里的 6 是按字节算的,中文在 UTF-8 下占 3 个字节,所以“张三”两个字是 6。搞懂这一点,你才算真的理解了 PHP 字符串的底层表示。

8.4 案例四:用 PHP 内置服务器快速演示接口返回

不用装 Apache,直接用 PHP 自带服务器加一个接口演示:

<?php // api.php header('Content-Type: application/json; charset=utf-8'); $students = [ ['id' => 1, 'name' => '张三', 'score' => 88], ['id' => 2, 'name' => '李四', 'score' => 72], ['id' => 3, 'name' => '王五', 'score' => 95], ]; // 按分数排序,降序 usort($students, function ($a, $b) { return $b['score'] <=> $a['score']; }); echo json_encode([ 'code' => 0, 'msg' => 'success', 'data' => $students ], JSON_UNESCAPED_UNICODE);

终端执行php -S localhost:8080,浏览器访问http://localhost:8080/api.php就能看到 JSON 数据。这里用到header设置响应头、usort自定义排序、闭包、<=>太空船运算符、json_encode输出中文。可以说这个 20 行的小接口把前面 80% 的语法点都串起来了。

9. 常见问题与排查技巧实录

9.1 “空白页”到底怎么排查

PHP 报错如果开了display_errors,会直接显示。但生产环境或配置不对时,你可能只看到白屏。这时候快速定位方法:在文件开头加:

ini_set('display_errors', 1); error_reporting(E_ALL);

还有一招是检查浏览器开发者工具的网络面板。白屏不一定是你代码有问题,也可能是header输出重定向了,或者 AJAX 接口返回了 500。打开 Network 面板看 HTTP 状态码,比瞎改代码高效得多。

9.2 为什么 HTML 里嵌 PHP 经常莫名多出空行

很多人发现页面顶部或两个模块之间无故空行,这种问题九成出在?>后面的换行。PHP 会把?>之后的内容原样输出,包括那个换行符。如果你在?>后又打了几个空行,这些空行就变成 HTML 内容输出了。我之前说过结尾标签建议省略,这就是实际原因。

9.3 中文乱码的排查路径

中文乱码有两类:页面乱码和数据库乱码。页面乱码通常是编码声明不一致造成的,HTML 文件要在<head>里写<meta charset="UTF-8">,PHP 文件本身保存为 UTF-8 无 BOM 格式。数据库乱码则要统一连接编码:

mysqli_set_charset($conn, 'utf8mb4');

如果 JSON 返回值里\uXXXX一堆,那是在json_encode时没加JSON_UNESCAPED_UNICODE。记住这条线:文件编码、页面声明、数据库连接、JSON 输出,四个环节编码不一致就会乱。

9.4 用 vscode 写 PHP 提示“php 不是内部或外部命令”

这个热搜词很典型。你装了 phpstudy 但在 VSCode 终端里执行php -v报错,是因为 PHP 可执行文件路径没有加入系统环境变量。解决:找到 phpstudy 里对应版本的php.exe路径,比如D:\phpstudy_pro\Extensions\php\php8.1.2nts\php.exe,把它所在目录加到系统的 PATH 环境变量里,重新打开终端即可。

9.5 有同学问:能不能用 NetBeans 写 PHP?

可以,NetBeans 是专门支持 PHP 的 IDE,自带语法高亮、代码补全、调试器集成。但如今我强烈推荐 VSCode 加 PHP Intelephense 插件,免费、轻量、补全强大。别在“IDE 选择”上纠结超过半小时,能跑代码的工具就是好工具,重要的是你一天能写多少行然后踩多少个坑。

10. 我把这套语法基础用到项目里的几点心得

开始写自己的 PHP 作品集之前,我有几个经验想分享给正在看这篇“PHP 基础案例教程之 02-PHP 语法基础”的朋友。这些东西是语法书里不会告诉你的。

第一,一个变量用之前先想一想它的类型可能是什么。PHP 是弱类型,但不代表你可以放任不管。当一个变量可能在同一个请求里从字符串变成数组,再变成对象,那你的代码就是一颗定时炸弹。我在处理接口数据时,习惯立刻用json_decode($json, true)保证它是数组,后面所有操作都以数组方式访问。

第二,输出调试信息时慎用echo输出数组,用print_r或var_dump。别问我为什么这么强调——我看到太多人写echo $array;然后只得到一个Array字样,半天没反应过来要换成print_r。

第三,写函数时不要依赖外部变量。全局变量只在入口脚本和数据共享时用,普通业务逻辑能传参就传参。别的不说,后面你写单元测试的时候就知道好处了。

第四,把match、??、<=>、类型声明这些 PHP 8 特性真正用起来需要先把基础打牢。你至少要理解==和===的区别、变量赋值和引用赋值的区别,才有能力驾驭更高级的语法。地基不稳就上框架,遇到一个TypeError报错可能当场就懵了。

最后,我把这套语法实践沉淀成了一个小项目——一个围绕图书管理的极简增删改查页面,用到了$_POST、$_GET、PDO查询数据库、foreach循环输出表格、password_hash做管理员认证。自己完整写一遍,比看十篇教程都有用。学语法的目的从来不是背诵手册,而是能把你脑子里的想法用代码讲出来。把这篇文章里的案例抄下来、改改看、在本地跑一跑,比抱着视频“看会了”强太多。PHP 的语法没那么难,真正拉开差距的,是你动手踩坑的次数。

返回列表