2018年秋天我坐在京东的在线笔试系统前面对一套PHP开发工程师的笔试题第一反应是这公司是真的想招一个会写代码的人而不是招一个背过八股文的人。整套试卷单选、多选、填空、简答、编程题都有题目本身不冷门但坑挖得很细。后来我辅导过好几届准备大厂秋招的同学发现这套2018年的题特别适合拿来当作PHP开发工程师自测的试金石——它覆盖了语言基础、工程习惯、数据库设计和算法基本功能真实反映出一个人平时写代码的水平。这篇文章就结合当年这套题的考点和我的复盘笔记逐块拆解。1. 京东秋招PHP笔试题的试卷风格与难度画像1.1 试卷结构基础题占大头设计题决定上限这套题的整体结构是选择题、简答题、编程题混合。选择题大概有30道左右覆盖面很广PHP语法细节、常用函数、数组和字符串操作、正则表达式、SQL语句、HTTP协议、Linux基础命令都会出现。简答题一般两到三道经常让你手写一个PDO封装类或者解释PHP错误处理机制。最后是编程题通常是LeetCode简单到中等难度的算法题也有偏业务场景的字符串处理题。从题型分配能看出出题人的思路基础题数量大但分值分散属于“人人都会碰一点但能不能拿全分就看你基本功”设计题分值集中考察的是实际工程里能不能写出可维护的代码算法题则是大厂筛人的硬指标。很多人备考时只刷算法忽视了PHP基础结果前面选择题错得一塌糊涂后面编程题也没时间做非常可惜。1.2 难度画像广度大于深度但坑都在细节这套题看起来不难因为大部分考点在PHP手册里都有可一旦落到具体代码输出题上就容易翻车。出题人很清楚PHP这门语言入门门槛低但真正写得好的人不多所以考察的不是“能不能写出hello world”而是“知不知道数组指针在foreach之后的位置变化”“unset之后变量引用会不会断裂”“error_reporting和try-catch的关系”这类细节。我当时备考把PHP手册过了一遍但真正做题时还是会发现有些细节记得不牢。后来我总结出一条经验这类大厂笔试靠猜和蒙是过不了的基础必须扎实到条件反射。当年考生搜得最多的几个热词像“php错误处理”“php队列”“php 数据库pdo访问封装类下载”恰好就是这套题的重点方向这也能看出大家普遍在哪些地方容易卡壳。2. 基础语法题看似送分全是陷阱2.1 变量引用与写时复制两种代码两种结果选择题最喜欢出这类题给你一段代码问输出什么。比如最简单的一种$a hello; $b $a; $a world; echo $b; // 输出 hello这个结果大家都能答对。但如果换成引用$a hello; $b $a; $a world; echo $b; // 输出 world很多人会在这里犹豫。原因在于PHP变量的底层机制是“写时复制”普通赋值时并没有真正复制一份内存而是让两个变量名指向同一个内容只有当其中某个变量被修改时才真正复制。引用赋值则直接打破这个机制让两个变量名指向同一块内存任何一方的修改都会影响另一方。笔试里这个知识点常见的变形还包括数组和对象的赋值。PHP中对象默认就是“引用传递”的语义但数组是值拷贝也是写时复制。有同学把对象和数组搞混一看到对象赋值就以为传引用其实对象本身传的是对象标识符不算严格引用。做题的时候一定要先分清楚变量类型再看代码里有没有符号。2.2 数组指针与foreach的隐性引用数组指针是PHP笔试里特别经典的一个坑。current()返回数组中当前元素的值next()把指针向后移一位reset()让指针回到开头。题目经常会让你写出一段循环操作后数组指针指向哪里这考的是对数组内部机制的掌握。更经典的是foreach配合引用变量产生的陷阱几乎每个考PHP的人都会遇到。看这段代码$arr [1, 2, 3]; foreach ($arr as $v) { $v; } foreach ($arr as $v) { echo $v; }如果你没有经验可能会以为输出是“234”因为第一个循环把每个元素都加1了。但实际上输出是“233”。原因在于第一个foreach循环结束后$v仍然引用着数组的最后一个元素也就是$arr[2]。第二个foreach第一次迭代时把$arr[0]的值2赋给$v相当于把$arr[2]改成了2第二次迭代把$arr[1]的值3赋给$v$arr[2]变成3第三次迭代取到$arr[2]已经变成3所以输出是233。这种题考察的不是死记硬背而是你有没有真正理解变量引用。答题技巧也很简单foreach用引用遍历后记得用unset($v)释放引用。这个细节在平时写代码时很容易被忽略但一旦上线可能造成很难排查的隐蔽bug。2.3 字符串函数边界substr的负数参数与编码陷阱substr是PHP笔试中的常客。最常见的考法是负参数echo substr(abcdef, -3); // 输出 def echo substr(abcdef, 2, -1); // 输出 cde echo substr(abcdef, -4, 2); // 输出 cd这些规则在PHP手册里有但平时不写这类代码的人很容易记混。更坑的是中文截断问题substr(你好世界, 0, 3)在UTF-8环境下会把“你”字截掉一半因为一个UTF-8中文占3个字节而substr按字节切不按字符切。正确答案应该是用mb_substr(你好世界, 0, 2)才能输出“你好”。笔试中遇到字符串函数不要急着写答案先看题目里有没有提示字符集。如果题目说“UTF-8”那多半就是在考mb_substr。业务代码里也建议统一使用mbstring扩展提供的函数避免不同编码下出现乱码或截断。3. 面向对象与错误处理考察工程化思维3.1 面向对象设计接口、抽象类、Trait的分工面向对象部分京东这套题不怎么考概念默写而是给你一段代码让你指出它的设计问题或者补全某个抽象方法。这里最基础也最容易丢分的是分不清interface、abstract class和trait的使用场景。老规矩先看区别类型能不能实例化能不能有实现典型用途interface不能只能定义方法签名定义契约abstract class不能可以部分实现定义模板沉淀公共逻辑trait不能可以有完整实现代码复用组合使用很多团队在写业务代码时习惯所有类都继承一个BaseController但这样会导致继承层级特别深改动基类影响面很大。使用trait可以按需组合方法把公共代码抽出来。笔试题如果问到“你觉得这段代码有什么问题”你除了说命名规范还可以从设计原则角度回答类是否违反单一职责接口是否过于臃肿是否应该依赖抽象而不是具体类魔术方法也是高频考点。面试官很喜欢问__construct、__get、__set、__call、__callStatic、__invoke、__toString的作用。最容易被忽略的是__invoke当一个对象被当作函数调用时触发。比如class CallableClass { public function __invoke($x) { return $x * 2; } } $obj new CallableClass(); echo $obj(5); // 输出 10这种写法在一些框架容器里很常见。答对这类题不需要背手册而是要见过实际场景所以平时看框架源码时多留个心眼。3.2 错误处理异常与错误的两个世界“php错误处理”是当年考生搜得最多的关键词之一因为PHP的错误机制确实和很多语言不一样。在Java里所有异常都是try-catch但在PHP里错误和异常是两套体系虽然它们最终都实现了Throwable接口。简单理解Exception业务异常应该用try-catch捕获并处理。ErrorPHP7开始实现Throwable类不存在、函数不存在这类致命错误也可以被try-catch捕获。Notice、Warning属于错误级别不是异常try-catch捕不住要用error_reporting设置报告级别或者用set_error_handler自定义错误处理函数。Fatal Error大多数情况下无法捕获可以注册register_shutdown_function在脚本结束时做善后。下面这段代码是我在实际项目中常用的错误转异常写法set_error_handler(function ($severity, $message, $file, $line) { throw new ErrorException($message, 0, $severity, $file, $line); }); try { // 任意代码 file_get_contents(/no/such/file); } catch (ErrorException $e) { echo 捕获到错误 . $e-getMessage(); }笔试题如果问error_reporting(E_ALL)和ini_set(display_errors, 0)有什么区别要答清楚前者控制记录哪些错误后者控制是否把错误输出到页面。生产环境必须把display_errors关掉否则报错信息会直接暴露给用户很可能带上SQL语句或文件路径有安全风险。4. 数据库与常见功能设计题PDO封装、队列、图书管理4.1 手写PDO封装类别只写new PDO要写单例和预处理京东这类电商大厂的后端笔试极大概率会让你写一个数据库操作类。我见过很多同学在试卷上写$pdo new PDO(mysql:hostlocalhost;dbnametest, root, password);这样写可以拿一点分但离及格线很远。真正有工程经验的回答会包含三件事单例模式、预处理语句、异常处理。一个比较完整的版本是class Database { private static $instance null; private $pdo; private function __construct($dsn, $user, $pass) { try { $this-pdo new PDO($dsn, $user, $pass); $this-pdo-setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this-pdo-setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); } catch (PDOException $e) { // 这里不要直接输出异常信息最好记日志 throw new RuntimeException(数据库连接失败); } } public static function getInstance($dsn, $user, $pass) { if (self::$instance null) { self::$instance new self($dsn, $user, $pass); } return self::$instance; } public function query($sql, $params []) { $stmt $this-pdo-prepare($sql); $stmt-execute($params); return $stmt; } private function __clone() { } }为什么构造函数必须私有因为单例模式的核心就是外部不能随便new只能通过getInstance拿同一个实例。__clone私有化是为了防止别人通过clone复制出一个新对象破坏单例状态。query方法里的预处理绑定是防SQL注入的关键任何直接把变量拼进SQL的写法都应该被标记为不合格。这道题想考察的其实是你在真实项目里会不会写安全、可维护的代码。如果你还能顺带说一句“生产环境应该把数据库配置放在环境变量里而不是写在代码中”那更会让面试官眼前一亮。4.2 队列设计数据结构和消息队列选型电商系统离不开队列京东笔试也常常会问“如何实现一个消息队列”或者“请设计一个简单的任务队列”。这种题没有唯一答案关键是要分层次回答。最简单的方案是用PHP数组模拟array_push入队array_shift出队。但array_shift会把数组重新索引时间复杂度是O(n)数据量大了以后非常慢。更合理的内置结构是SplQueue它底层的性能更好语义也更清晰$queue new SplQueue(); $queue-enqueue(task1); $queue-enqueue(task2); $task $queue-dequeue();到了生产环境单机内存队列扛不住并发和持久化需求一般会用Redis的list结构实现。入队用LPUSH出队用BRPOP可以阻塞等待天然支持多个消费者轮询消费。如果要求更高的可靠性就需要用RabbitMQ或Kafka这类消息中间件。答题时不要只写代码最好把技术选型和业务场景绑在一起讲清楚。比如异步邮件通知Redis队列即可丢了还能重发。订单超时关闭Redis的有序集合或RabbitMQ的延迟队列更合适。日志收集选Kafka吞吐量高能扛海量写入。这种设计思路比单纯堆术语更能体现你的架构意识。4.3 图书管理系统的表结构设计从业务需求到三范式热词里有“php图书管理系统”这种经典业务题在笔试题里也出现过。假设要求你设计图书管理系统的数据库表管理图书、读者和借阅记录很多人的第一反应是建一张大表把书的信息和读者的信息都塞进去。这样虽然查询方便但会产生严重的数据冗余同一本书被借了100次书名、作者、ISBN就要重复存100遍。一旦书名修改所有历史记录都要跟着改容易造成数据不一致。更好的设计是拆成三张表表名字段说明bookbook_id, title, author, isbn, stock图书主表stock是库存数量readerreader_id, name, phone, created_at读者信息borrowborrow_id, book_id, reader_id, borrow_date, return_date借阅流水这种设计符合数据库三范式每张表只存自己关心的数据借阅表通过外键关联图书和读者。实际开发中索引也很重要borrow表的book_id、reader_id都要建索引否则搜索借阅记录时会全表扫描。如果考虑性能压测也可以针对高频查询场景增加冗余字段比如在借阅表冗余一本书的书名便于直接展示但这属于反范式优化笔试时先答三范式设计再补充“可以根据业务做冗余”。5. 编程题实战从审题到边界测试的完整链路5.1 一道典型的字符串处理题从明确输入输出到写防御代码编程题部分京东这套题给的算法题并不算难但很考验代码风格和边界处理。我记得有一类题目非常贴近业务实现一个函数把字符串中的连续重复空格替换为单个空格。题目描述看起来简单但很多人一上来就写正则function removeDuplicateSpaces(string $str): string { return preg_replace(/\s/, , $str); }正则解法没问题但如果你还要处理字符串首尾空格就得额外加trim。笔试题想看的其实是你有没有系统性的解题思路而不是只会塞正则。我建议在试卷上先写一段纯手写的实现function removeDuplicateSpaces(string $str): string { $result ; $prevSpace false; $length strlen($str); for ($i 0; $i $length; $i) { $char $str[$i]; if ($char ) { if ($prevSpace) { continue; } $prevSpace true; } else { $prevSpace false; } $result . $char; } return $result; }接着你要写出测试用例removeDuplicateSpaces(hello world)应该返回hello world。removeDuplicateSpaces( a b )如果需求是压缩连续空格但不处理首尾应该返回 a b 。removeDuplicateSpaces()应该返回空字符串不能报错。removeDuplicateSpaces( )应该返回一个空格。写出这些边界用例比只写一个实现要加分得多。因为面试官能看到你会不会考虑空输入、全空格、连续空格、首尾空格这些情况这就是工程思维。5.2 手写代码时的通用踩坑点变量作用域、空值判断、数据类型笔试编程题想要拿高分除了算法正确代码细节也不能忽略。我自己在复盘时总结了几个高频踩坑点第一循环变量作用域。PHP没有块级作用域for循环里定义的变量循环结束后依然存在。前面提到的foreach引用问题就是作用域和引用结合的典型案例。代码里如果定义了$result最好初始化否则PHP会给出Notice提示。第二空值判断要区分null、0、、false、空数组。empty()和isset()的差别是必考考点。empty(0)为trueisset(0)为true很多人搞混。写代码时根据题目语义选择判断方式不要一律用! null。第三字符串按字节访问。$str[$i]在PHP中能访问字符串中的字节但如果字符串是UTF-8中文$str[0]只能拿到第一个字节而不是第一个字符。遇到中文字符串处理要使用preg_split(//u, $str)或mb_str_split。第四循环里注意不要直接修改正在遍历的数组。如果非要在遍历时删除元素用foreach unset是安全的但不要用for unset因为数组长度会动态变化很容易漏元素或者访问越界。第五函数返回类型声明。PHP 7之后建议显式写出返回类型比如function foo(): array。笔试代码里写出类型声明会显得你平时就是按工程标准写代码的而不是脚本临时工。6. 复盘后的几点应试经验与后续学习路线6.1 时间分配先做会的再啃硬骨头这套卷子在线笔试时间大概90分钟时间并不宽裕。我的建议是选择题每道控制在1分钟内遇到拿不准的立即标记并跳过不要在一道题上死磕。简答题先写关键点把代码和结论写出来再补理由。编程题最后做因为分值最高而且容易写时间长一定要先审清输入输出再动手。我见过不少同学在选择题上花了太多时间最后编程题只写了个开头非常亏。笔试不是面试系统不会等你要把“先易后难”刻在脑子里。6.2 复习重点与常见资源如果你想通过这类大厂PHP笔试建议按下面的优先级复习PHP语言基础数组、字符串、变量引用、作用域、魔术方法。数据库索引原理、SQL优化、PDO使用。网络协议HTTP状态码、请求方法、Cookie与Session。算法数组、字符串、链表、二叉树、动态规划入门。业务设计单例模式、工厂模式、队列、缓存、Reda。我当时主要啃PHP官方手册和《PHP核心技术与最佳实践》再把LeetCode题库里简单和中等难度的数组、字符串题刷一遍就基本能覆盖笔试了。热词里出现“thinkphp3.2.3”很多同学以为要复习框架源码其实笔试很少直接考框架版本框架只是工具底层PHP知识才是重点。6.3 面试之后的动作记录错题、回补盲点每一次笔试结束我都会在当天晚上把能记住的题目全部回忆出来整理成错题本。过几天网上通常也会有回忆版讨论帖再对照着补充。这个方法帮我发现了不少盲区比如有一次我发现自己对array_map和array_filter的回调参数顺序一直记混专门整理了一页对比笔记。最后一次用这套题做模拟是在去年有个学弟做完之后感慨“没想到一个输出题能错得这么离谱。”我觉得这就是它的价值——让你发现你以为自己会、其实不会的知识点。如果你也要准备PHP大厂笔试与其刷一堆偏题怪题不如把这份复盘里提到的基础细节每一个都吃透。笔试没过不代表你不优秀很多时候只是输在那些琐碎的陷阱上而这些陷阱完全可以通过系统性复盘提前排掉。