下面是面试中最常出现的 PHP 问题,答案简短,并且在 PHP 8.3 下是正确的;需要用代码说明的地方都附有代码。按下“运行”之前,先预测每段代码的输出:面试官考的正是这个。
PHP 初级面试题
PHP 是什么?它是怎么运行的?
PHP 是一种服务器端脚本语言。Web 服务器(Nginx 或 Apache)把请求交给 PHP(通常是 PHP-FPM),PHP 从头开始运行脚本,它输出的内容就成为响应,通常是 HTML。每个请求都从零开始:除非把变量存进 session、数据库或缓存,否则它们不会在请求之间保留。PHP 也可以在命令行中运行脚本(php script.php)。
== 和 === 有什么区别?
== 在类型转换之后再比较,=== 比较值和类型,不做任何转换。上面的热身题展示了 == 的效果:数字字符串会按数字比较,所以 '10' == '1e1' 为 true。从 PHP 8 开始,数字和非数字字符串比较时按字符串比较,所以 0 == 'a' 为 false(在 PHP 7 中为 true)。点击单元格可以对比两个运算符:
| $a == $b | true | false | 0 | 1 | "0" | "1" | "" | null | [] | "a" | "10" | "1e1" |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| true | ||||||||||||
| false | ||||||||||||
| 0 | ||||||||||||
| 1 | ||||||||||||
| "0" | ||||||||||||
| "1" | ||||||||||||
| "" | ||||||||||||
| null | ||||||||||||
| [] | ||||||||||||
| "a" | ||||||||||||
| "10" | ||||||||||||
| "1e1" |
默认使用 ===。in_array() 和 array_search() 使用 ==,除非你把 true 作为第三个参数传入。
echo 和 print 有什么区别?
两者都输出字符串。echo 可以接受多个参数(echo $a, $b;),没有返回值;print 只接受一个参数并返回 1,所以可以用在表达式中。它们都不是函数,所以括号可写可不写。实际开发中大家都用 echo。
单引号和双引号有什么区别?
双引号字符串会解析变量和转义序列:"Hi $name\n"。单引号字符串除了 \' 和 \\ 之外都按字面输出:'Hi $name\n' 会原样打印这些字符。两者的速度差异可以忽略,所以选择取决于可读性:需要插入变量时用双引号,不需要时用单引号。
PHP 有哪些数据类型?
四种标量类型(int、float、string、bool),两种复合类型(array 和 object),以及两种特殊类型(null 和 resource)。callable、iterable、mixed 和 void 只用于类型声明,从来不是某个值的类型。gettype() 和 get_debug_type() 可以在运行时告诉你一个值的类型,var_dump() 会把类型和值一起打印出来。
isset、empty 和 is_null 有什么区别?
变量存在且不为 null 时,isset($x) 为 true。变量不存在或为假值(0、'0'、''、[]、false、null)时,empty($x) 为 true。is_null($x) 对 null 为 true,但遇到未定义的变量会发出警告。isset 和 empty 从不发出警告,所以常用来检查数组键。
| $x | isset | empty | is_null |
|---|---|---|---|
| null | |||
| false | |||
| 0 | |||
| "0" | |||
| "" | |||
| " " | |||
| [] | |||
| "a" | |||
| 0.0 |
大家最容易答错的是 "0" 这一行:字符串零是 empty,而 " "(一个空格)不是。isset 页面 有完整的表格。
include 和 require 有什么区别?
两者都会插入并运行另一个文件。如果文件不存在,include 会发出警告,脚本继续运行;而 require 会抛出 Error("Failed opening required"),除非有代码捕获它,否则脚本就会停止。带 _once 的版本(include_once、require_once)会跳过已经加载过的文件,从而避免 "cannot redeclare function" 错误。对于脚本离不开的文件(几乎所有文件都是),使用 require(或 require_once);有了 Composer 的自动加载器,大多数手动引入都不再需要。
GET 和 POST 有什么区别?
GET 把数据放在 URL 的查询字符串中发送($_GET),因此可见、可收藏,长度也有限制;它适合读取操作,例如搜索和分页。POST 把数据放在请求体中发送($_POST);任何会改变状态的操作都用它,例如登录、保存或支付。GET 请求永远不应该修改数据,因为链接会被预取和爬取。
空合并运算符是什么?
如果 $a 存在且不为 null,$a ?? $b 返回 $a,否则返回 $b,遇到未定义的变量或键也不会警告:$page = $_GET['page'] ?? 1;。??= 只在左侧为 null 或不存在时才赋值。它和 ?: 不同,后者遇到任何假值都会取后备值,所以 0 ?: 5 是 5,而 0 ?? 5 是 0。
PHP 中级面试题
在 foreach 中按引用循环会发生什么?
foreach ($arr as &$v) 让你可以直接修改元素,但循环结束后 $v 仍然是最后一个元素的引用。在第二个循环中再用这个变量名,就会覆盖那个元素:
最后一个元素是 20,而不是 30:在第二个循环中,$v(仍然是 $arr[2])先被赋值为 10,然后是 20,最后是它自己。删掉 unset($v) 前面的 // 再运行一次。foreach 页面 详细介绍了引用。
抽象类和接口有什么区别?trait 又是什么?
接口是一份契约:只有方法签名(和常量),没有实现,一个类可以实现多个接口。抽象类可以有属性、构造函数和已实现的方法,还有子类必须实现的抽象方法;一个类只能继承一个抽象类。trait 是一组用 use 复制进类中的方法,用于在没有共同父类的类之间复用代码。
面试官喜欢的简短回答:用接口说明一个东西能做什么,用抽象类共享怎么做,当互不相关的类需要同样几个方法时用 trait。
self 和 static 有什么区别?
self 指代码所在的类。static 指运行时实际被调用的类,这叫作后期静态绑定。在父类中创建或描述“这类对象”的方法里,两者的区别很重要:
User::createStatic() 创建的是 User。User::createSelf() 创建的是 Model,这违反了 static 返回类型,所以 PHP 抛出 TypeError。这就是工厂方法使用 new static() 的原因。
魔术方法是什么?
由 PHP 替你调用的方法,名字都以两个下划线开头。常见的有:创建对象时调用的 __construct(),对象销毁时调用的 __destruct(),对象被当作字符串使用时调用的 __toString(),访问不可访问属性时调用的 __get() 和 __set(),调用不存在的方法时调用的 __call() 和 __callStatic(),对象被当作函数调用时的 __invoke(),以及 clone 之后调用的 __clone()。
array_merge() 和 + 运算符有什么区别?
array_merge() 是追加:数字键会重新编号,后面的字符串键会覆盖前面的。+ 是并集:它保留左边的数组,只添加缺少的键,所以遇到重复的键时,第一个值胜出。
+ 很适合用来设置默认值:$options + ['timeout' => 30] 会保留调用者传入的值,并补上其余的。
declare(strict_types=1) 有什么作用?
没有它时,PHP 会在可能的情况下把标量参数转换成声明的参数类型:把 "5" 传给 int 参数会得到 5。在文件的第一条语句写上 declare(strict_types=1); 后,从该文件发出的调用必须传入完全匹配的类型,否则会抛出 TypeError(把 int 传给 float 参数是唯一允许的拓宽)。
删掉 declare 这一行再运行一次:第二次调用会返回 10,而不是抛出异常。
session 和 cookie 有什么区别?
cookie 存储在浏览器中,每次请求都会发送,所以访问者可以读取和修改它。session 把数据存储在服务器上,只在 cookie 中保存一个随机 id(PHPSESSID)。凡是必须可信的数据,例如已登录用户的 id,都放进 $_SESSION;登录后调用 session_regenerate_id(true)。session 和 cookie 页面 有相关代码。
如何防止 SQL 注入?
永远不要通过拼接输入来构造 SQL。使用预处理语句,让值和查询分开发送:
<?php
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch();
占位符只能用于值。表名、列名以及 ASC/DESC 必须先对照允许列表检查,然后才能放进 SQL。参见 PDO。
如何防止 XSS?
转义输出,而不是输入:每个输出到 HTML 中的值都用 htmlspecialchars($value, ENT_QUOTES, 'UTF-8') 包起来,或者使用会自动转义的模板引擎(Twig、Blade)。在 <script> 中,使用带 JSON_HEX_TAG 的 json_encode()。再加一个 Content-Security-Policy 响应头作为第二层防护。
密码应该如何存储?
用 password_hash($password, PASSWORD_DEFAULT) 存储,用 password_verify($password, $hash) 校验。它们使用 bcrypt(或 Argon2id),随机盐保存在哈希值内部,并有刻意设置的计算成本。md5() 和 sha1() 不适合用于密码,因为它们速度快且不加盐。登录时用 password_needs_rehash() 升级旧的哈希。
PHP 高级面试题
生成器是什么?什么时候使用?
包含 yield 的函数会返回一个 Generator,它一次产生一个值,而且只在需要时才产生。这样你可以遍历一个很大甚至无穷的序列,而不必在内存中构建数组:
典型用途有逐行读取大文件、流式读取数据库的行,以及惰性处理管道。生成器还支持键(yield $key => $value)、委托(yield from)以及用 send() 接收值。
闭包是什么?use 有什么作用?
匿名函数(function () { ... })是一个 Closure 对象。它看不到外部变量,除非你在 use 中列出它们;use 会在闭包创建时复制变量的值,而 use (&$x) 则按引用捕获。箭头函数(fn ($x) => $x * $factor)会自动按值捕获外部变量,并且只包含一个表达式。
abstract、final 和 readonly 有什么区别?
abstract 标记不能实例化的类,或者子类必须实现的方法。final 禁止重写方法或继承类。readonly(PHP 8.1)让属性只能从类内部写入一次,通常在构造函数中;PHP 8.2 新增了 readonly 类,其中的每个属性都是只读的。
Composer 是什么?PSR-4 又是什么?
Composer 是 PHP 的依赖管理工具:composer.json 列出所需的包,composer.lock 锁定确切的版本,vendor/autoload.php 负责加载所有内容。PSR-4 是自动加载标准,它把命名空间前缀映射到文件夹,所以 App\Mail\Mailer 位于 src/Mail/Mailer.php,并在第一次使用时加载。还要能讲清楚 install(按锁文件安装)和 update(获取新版本并生成新的锁文件)的区别。
PHP 8 有哪些变化?
主要特性:8.0 带来了命名参数、match、空安全运算符 ?->、联合类型、构造函数属性提升、注解(attributes)、str_contains(),以及更合理的字符串与数字比较;8.1 带来了枚举、只读属性、一等可调用语法(strlen(...))和 Fiber;8.2 带来了只读类以及独立的 null/true/false 类型;8.3 带来了类型化类常量、#[\Override] 注解和 json_validate();8.4 带来了属性钩子、非对称可见性(public private(set))、array_find(),以及无需外层括号的 new Foo()->method()。许多函数现在也会抛出 TypeError 或 ValueError,而不再是返回 false 并发出警告。
PHP 如何管理内存?
每个值都有一个引用计数;计数降到零时,内存会立即释放。相互引用形成循环的对象永远不会降到零,所以每当可能的循环根缓冲区填满时(默认 10,000 个条目),循环回收器就会运行并释放它们(gc_collect_cycles() 可以手动触发)。数组和字符串采用写时复制:$b = $a 赋值后两者共享数据,直到其中一个被修改。由于每个请求的开始和结束都很干净,内存泄漏主要在长时间运行的 worker 和队列消费者中才需要关注。
PHP 数组是如何实现的?
PHP 数组是一个有序的哈希表:它保持插入顺序,把 int 或 string 键映射到值,所以同一种类型既能当列表,也能当字典和栈。按键查找(isset($arr[$key]))平均是常数时间,而 in_array() 会扫描每个值。像整数的字符串键会被转换:$a['1'] 和 $a[1] 是同一个元素。
常见的面试编程题
现场面试中经常出现的小练习。每个函数都是一种常见解法;运行它们,加入你自己的输入(包括空输入),并准备好解释时间复杂度,这通常才是面试官真正要考察的。
常见问题
PHP 开发面试该怎么准备?
确保不查资料也能讲清楚并写出这些基础知识的代码:比较与类型、数组及其函数、面向对象(接口、抽象类、trait、static)、异常,以及安全(预处理语句、htmlspecialchars、password_hash)。然后熟悉一个框架(Laravel 或 Symfony)和 Composer,能够讲清楚你做过的一个项目。
面试时应该掌握哪个 PHP 版本?
PHP 8。面试官希望你了解 8.0 至 8.3 新增的特性:命名参数、match、空安全运算符 ?->、联合类型、构造函数属性提升、枚举、只读属性和一等可调用语法。只在行为有变化的地方提一下 PHP 7,例如 0 == "a"。
应届生 PHP 面试会问哪些问题?
主要是语言基础:== 与 ===、echo 与 print、单引号与双引号、isset 与 empty、include 与 require、GET 与 POST、session 与 cookie,以及反转、过滤、统计值之类的简单数组题。至少会有一道简短的编程题。
有经验的开发者 PHP 面试会问哪些问题?
设计以及底层行为:接口与抽象类、trait、后期静态绑定、引用、生成器与内存、异常和错误处理、SQL 注入和事务、缓存、Composer 自动加载,以及你会如何组织或测试一个功能。回答时要讲清楚取舍,而不只是背定义。