Menu

PHP 面试题及答案(PHP 8)

24 道 PHP 面试题,附简短且适用于 PHP 8 的正确答案,从 == 与 ===、isset 与 empty,到 foreach 中的引用、self 与 static、生成器、SQL 注入和密码哈希。大多数答案都附有可以运行和修改的代码。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

下面是面试中最常出现的 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)。点击单元格可以对比两个运算符:

松散比较(==)与严格比较(===)
Point at or tap a cell to read it as PHP.
$a == $btruefalse01"0""1"""null[]"a""10""1e1"
true
false
0
1
"0"
"1"
""
null
[]
"a"
"10"
"1e1"
Every cell computed by PHP 8.3.19.

默认使用 ===。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
Point at or tap a cell to read it as PHP.
$xissetemptyis_null
null
false
0
"0"
""
" "
[]
"a"
0.0
Every cell computed by PHP 8.3.19.

大家最容易答错的是 "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,而不是抛出异常。

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 自动加载,以及你会如何组织或测试一个功能。回答时要讲清楚取舍,而不只是背定义。

Coddy 编程语言插图

和 Coddy 一起学编程

开始