这套“欢聚时代2017校招笔试题目(PHP工程师类)A卷”,我前前后后看了不下五遍。当时是帮一个学弟做考前突击,他把卷子拍照发我,我一边看一边感慨,这份卷子的出题人绝对是懂PHP的,没有一味追求偏题怪题,考察的全是一个后端开发每天都要打交道的基础内功。这年头网上能找到的PHP面试题集大多零散,要么只给答案不讲原理,要么题目老旧还在问PHP4的写法。所以今天干脆把这份卷子从头到尾拆一遍,结合我对PHP这门语言的理解,把每个考点背后的设计逻辑、答题思路、容易踩的坑都补全,希望能帮到正在准备校招或跳槽的同学。
先说这份卷子的背景。欢聚时代,老玩家都叫它YY,当年凭借YY语音在实时互动领域起家,后来又孵化出虎牙直播,对后端高并发、实时消息推送的要求非常高。2017年的校招笔试A卷,PHP工程师岗位,整体风格是典型的互联网公司技术笔试:题量大、基础面广、有几道需要手写代码的算法题,外加一道业务场景设计题。和很多公司喜欢出“你用过哪些框架”“说一个你熟悉的函数”这种开放性题目不同,这份卷子更偏向用具体的小例子考察候选人的基本功,尤其是在弱类型、引用、传值传址、数组运算这些容易混淆的细节上反复做文章。
这其实是很多PHP程序员最头疼的地方。业务写熟了,CRUD溜得起飞,但一落到==和===的区别、self::和static::的区别、array_merge和+的区别,反而容易卡壳。原因是日常开发中大多数时候不会刻意去碰这些边界场景,但笔试恰恰喜欢考这些,因为它们能快速筛出“背过面试题”和“真正理解语言特性”的人。下面我按照卷面的题型分布,一个模块一个模块说。
1. 从这套题看欢聚时代校招的出题风格
1.1 为什么互联网公司喜欢用基础题筛选PHP候选人
很多同学拿到卷子第一反应是:这题也太基础了吧,为什么不考ThinkPHP的写法或者Laravel的中间件原理?这就是没想明白校招笔试的目的。校招进来的应届生,公司默认你项目经验有限,框架可以入职后学,业务逻辑可以师傅带,但语言底子如果不行,后续带起来会非常吃力。
PHP作为一门动态弱类型语言,它的很多特性是一把双刃剑。用好了开发效率极高,用不好就是线上事故的温床。比如==和===搞混,可能导致权限判断失效;empty和isset用错,可能判断不出参数是否存在;$_REQUEST误用,可能导致CSRF风险。欢聚时代这种体量的公司,后端接口动辄每秒几千上万的请求量,代码里任何一个隐性问题都会被放大。所以笔试题目看起来在考语法,实际上是在考“你有没有建立一套严谨的编程习惯”。
1.2 A卷的整体题型分布与考察权重
从当年这份卷子的结构来看,大致可以分为五块。第一块是选择题,覆盖PHP基础语法、常用函数、弱类型比较、数组操作;第二块是填空题,集中在字符串处理、魔术方法、SESSION机制;第三块是简答题,考察MySQL索引原理、SQL注入防御、HTTP协议流程;第四块是手写算法题,通常两三道,用PHP实现基础数据结构和算法;最后一道是业务设计题,结合欢聚时代的业务场景,让你谈谈高并发下某个功能怎么实现。
分值权重上,基础语法和语言特性大约占四成,数据库和网络占三成,算法占两成,业务设计占一成。这个比例很典型——基础不牢,后面的题基本拿不到分;基础扎实,即使算法题写不完美,前面的分已经足够进面试了。
2. 核心考点解析:PHP基础语法与语言特性
2.1 弱类型比较:这套题里最阴险的送命题
这份卷子的选择题里,我印象最深的一道是这样的:
$a = "10"; $b = 10; var_dump($a == $b); var_dump($a === $b);前半段$a == $b,字符串"10"和整数10比较,弱类型会自动把字符串转换成数字再比较,结果是true;后半段$a === $b,要求值和类型都相同,一个是字符串一个是整数,结果是false。这题本身不难,但它的变体非常多,比如考"10abc" "10"比较、“0e123”和“0e456”比较,甚至null == false这类。
我记得卷子里还有一道问$arr = [0, 1, 2]; var_dump(in_array("abc", $arr));的题目。PHP 8之前,in_array默认是松散比较,“abc”会被转换成0,所以结果是true,这是个非常经典的坑。如果在笔试里不写第三参数true,几乎必错。这类题一出来,就能看出你对PHP比较机制的敏感度。
我的建议:遇到比较运算题,先想清楚两边变量的类型,再想PHP的自动转换规则。只要有一边是布尔值,其他类型都会被转成布尔;没有布尔值的情况下,数值字符串转数字。养成这个思维习惯,相关题目基本不会失分。
2.2 PHP常用数组函数:看着简单,坑全藏在用法里
数组是PHP的核心数据结构,也是这套卷子填空和选择的重灾区。有两道题值得拿出来详细说。
第一道是array_merge和+运算的区别。给定两个数组$a = [0 => 'a', 1 => 'b']和$b = [1 => 'c', 2 => 'd'],分别做array_merge($a, $b)和$a + $b,结果是完全不同的。array_merge会重新索引数字键,得到['a', 'b', 'c', 'd'];而+运算符以左数组为准,保留左边已有的键,只补充右边没有的键,结果是[0 => 'a', 1 => 'b', 2 => 'd']。很多人在合并配置数组时踩过这个坑,尤其是用+合并框架配置文件时,后面的配置没覆盖成功,查半天才发现问题。
第二道是array_flip的陷阱。卷子里有一道填空题,大意是让你用一行代码去掉数组中的重复值。标准答案是array_keys(array_flip($arr)),因为array_flip会把值变成键,重复的值自然被覆盖掉,再取array_keys就得到去重后的结果。但这里有个关键前提:数组的值必须是字符串或整数,否则array_flip会直接报警告并返回false。这就是笔试题的狡猾之处,它考的不仅是你会不会用这个函数,还考你知不知道这个函数的边界条件。
2.3 字符串处理:笔试里的必考点
字符串函数在PHP日常开发中使用频率极高,校招卷子自然不会放过。卷子里考了strpos、substr、str_replace、explode、implode这几个基础函数,但问法很刁钻。
比如有一道题是:$pos = strpos("hello", "h"); if ($pos == false) { echo "not found"; },问输出什么。表面上看strpos找到了“h”在位置0,返回0,但用==和false比较时,0被转换成false,结果进入判断输出了"not found"。这就是老生常谈的“strpos返回值必须用===判断”的问题。这种代码在实际项目里太容易出现了,尤其是从参数里取子串判断是否存在时,一旦匹配位置是0就出bug。
另外有一个容易被忽略但笔试题爱考的:substr和mb_substr的区别。如果给一个包含中文的字符串做截取,substr会按字节截,中文字符在UTF-8下占3个字节,截不好就是乱码;mb_substr才是按字符截。欢聚时代当年有大量的聊天室和弹幕业务,处理用户昵称、弹幕内容这类文本数据时,这个问题躲不掉,所以出题人专门出了一道填空题考这个点。
3. 面向对象与PHP核心机制:拉开差距的地方
3.1 魔术方法、访问控制与继承:这些机制你是怎么理解的
PHP面向对象的笔试题通常不会太偏,考的都是最常用的机制,但这套卷子考得比较深。有道题让写出__construct、__destruct、__call、__get、__set、__toString的执行时机,并要求说明各自典型的使用场景。
这里有一个容易混淆的点是__call和__callStatic的区别。__call是在调用一个不存在或不可访问的实例方法时触发,__callStatic则对应静态方法。很多框架的链式操作、魔术方法代理就是靠这个实现的。比如TP框架里的模型查询,你调用一个不存在的whereName之类的方法,就是通过__call拦截解析成字段条件查询。笔试时如果能举出这种框架层面的例子,比干巴巴背定义要加分得多。
3.2 static、self、parent的定位差异:不得不知道的知识盲点
我记得这套卷子有一道选择题:
class A { public static function test() { echo "A"; } public static function callSelf() { self::test(); } public static function callStatic() { static::test(); } } class B extends A { public static function test() { echo "B"; } } B::callSelf(); B::callStatic();输出结果是A和B。这里self::在编译期就绑定到定义方法的类,也就是A;static::则是延迟静态绑定,在运行时才决定由哪个类调用,所以指向B。这种题如果只是背结论,很容易记混,最好的理解方式是记住一句话:self是“写代码时所在的类”,static是“运行时实际调用的类”。欢聚时代的业务里,大量模型基类、服务基类都会用到这个特性,比如基类里定义一个通用的创建方法,子类传入不同的表名或验证规则,靠static::实现多态。
3.3 手写单例模式:为什么这题总在校招笔试里出现
这套卷子的简答题里有一道手写单例模式的题,要求写出一个线程安全的单例类。PHP本身是单线程模型,一个请求生命周期内不会有多线程并发创建对象的问题,所以这里说的“线程安全”其实指的是防止在同一个进程内多次实例化。标准写法是:私有化构造函数,防止外部new;提供一个静态私有属性保存实例;通过一个静态方法判断实例是否已存在,不存在才创建;同时把__clone私有化,防止对象被克隆。
这里有个容易忽略的细节:单例类的构造函数里不能做太重的初始化操作,否则每次创建实例的开销还是会在。而且如果单例里持有数据库连接,长驻进程模式下要注意连接断开的处理。这些点写进答案里,会让面试官觉得你真的在项目里用过单例,而不是只会背模板。
3.4 错误处理机制:try/catch到底能抓什么
卷子里有一道判断题:用try/catch能捕获所有PHP错误吗?答案是错误的。PHP的try/catch只能捕获Exception和Throwable接口的实现类,传统的E_ERROR、E_WARNING、E_NOTICE这些错误级别不会触发异常,直接按错误级别输出或记录。到了PHP 7之后,Error类也实现了Throwable,所以TypeError、ParseError可以用try/catch捕获,但普通的warning和notice仍然不行。
这道题的隐藏考点是set_error_handler函数。PHP提供了一个自定义错误处理机制,可以把warning、notice转成ErrorException再抛出来,这样就能统一用try/catch管理。很多主流框架就是这么做的,比如Laravel的异常处理就是基于这个机制。答题时能把这一段讲清楚,比单纯答“能”或“不能”高级一个档次。
4. MySQL、网络协议与安全:后端工程师的基本功
4.1 MySQL索引原理与SQL优化分析
这套卷子的大题部分有关于索引的题目。问到“为什么InnoDB选择B+树作为索引结构,而不是哈希表或二叉树”——这几乎是每个后端岗位笔试面试必问的问题。答题思路要分三层:首先,哈希表虽然单点查询O(1),但不支持范围查询和排序;二叉树在数据量大时树高过高,磁盘IO次数太多;B+树的非叶子节点不存数据只存索引,一次磁盘IO能读入更多索引项,树更矮,而且叶子节点用链表串起来,天然支持范围查询和排序。
有了索引原理做铺垫,卷子里的第二问是联合索引和“最左前缀”原则。题目大意是:表里有a、b、c三个字段,已经建了(a, b, c)联合索引,问哪些查询条件能用到索引。答案是where a=1和where a=1 and b=2能用到,where b=2用不到索引,where a=1 and c=3只能用到a那一列。实际开发中很多人建了联合索引就不管了,结果查询条件里没有第一个字段,索引直接失效,慢查询一堆,这个问题在笔试里考,本质上还是想确认你有没有做过SQL优化。
还有一个高频考点是explain。卷子里让解释explain输出里的type字段几个常见值:const、ref、range、index、ALL。这题的答题逻辑是:ALL全表扫描最差,const是主键或唯一索引等值查询最好,ref是非唯一索引等值查询,range是索引范围扫描。线上慢查询优化的第一步就是看explain,如果type出现了ALL,说明索引设计有问题,要优先排查。
4.2 SESSION与Cookie的底层关系
SESSION和Cookie是PHP基础题里的常客,这套卷子出了一道简答题:用户登录后SESSION数据保存在哪里,客户端保存的是什么?
这个问题的完整答案分三层:第一层,SESSION数据默认保存在服务端的文件系统或Redis等缓存里,文件名和session_id关联;第二层,服务端通过Set-Cookie把PHPSESSID发给浏览器,浏览器后续每次请求都会带上这个Cookie;第三层,如果浏览器禁用了Cookie,PHP还支持通过URL参数传递session_id,但出于安全考虑,实际项目中很少这么干。
这里有一个隐藏扣分点:很多人只记住了“SESSION在服务端,Cookie在客户端”,却没说明服务端如何识别这个SESSION属于哪个用户,以及Cookie的HttpOnly和Secure属性对安全的影响。我在实际项目里见过不少因为没设HttpOnly导致XSS攻击直接偷走PHPSESSID的情况。在笔试题里把这几层答全,会显得你的经验不是停留在背题层面。
4.3 SQL注入、XSS与CSRF的防御思路
安全题在这份卷子里占的篇幅不大,但每一题都很有杀伤力。有一道简答是:为什么PDO预处理能有效防止SQL注入?这道题需要说清楚底层原理。预处理机制是先将SQL语句模板发送给MySQL服务器进行预编译,之后再绑定参数值。参数值和SQL模板是分开发送的,MySQL只把参数当成纯数据处理,不会参与SQL语法解析,所以单引号、注释符、OR 1=1这些东西就失去了改变SQL语义的能力。
XSS的题则更偏向实际场景。题目问:如果用户在你的弹幕功能里输入了一段带<script>标签的内容,不处理直接存库再输出到网页上,会发生什么?这就是一个典型的存储型XSS,脚本会在其他用户打开页面时执行,可能盗取Cookie、篡改页面、发起恶意请求。防御的核心就两条:输入侧尽量限制格式,输出侧做HTML实体转义。PHP里可以用htmlspecialchars($str, ENT_QUOTES, 'UTF-8'),把引号和尖括号都转义掉,框架里的e()函数、Blade模板的{{ }}默认转义也都是这个思路。
4.4 HTTP协议与一次完整请求的链路
简答题里有一道经典题:在浏览器输入一个网址并回车,到页面完全展示,中间发生了什么?这道题不限定PHP,但PHP后端开发答这道题,应该把技术栈的优势体现出来。
完整的链路包括:DNS解析域名到IP,建立TCP三次握手,如果是HTTPS还要经历TLS四次握手协商密钥,浏览器发起HTTP请求,经过负载均衡转发到Nginx,Nginx根据配置把PHP请求交给PHP-FPM,PHP-FPM启动或复用PHP进程执行脚本,脚本里连接MySQL、Redis,拿到数据后拼装HTML或JSON返回,最后浏览器解析渲染页面。答这道题的关键是“每一层都说清楚,但不用把每一层展开到源码级别”,因为面试官想考查的是你对自己写的代码在整个网络链路中处于什么位置有没有清晰认知。
5. 算法、Linux与业务场景题复盘
5.1 手写算法题:代码质量比跑通更重要
这套卷子的算法题不是LeetCode那种大厂标准困难题,而是两三个适合在纸上写的经典题。我印象里有二分查找、括号匹配、字符串反转。
二分查找这个题最要注意的就是边界条件。很多同学在纸上写循环版的二分查找,while ($left <= $right)写错成<,或者更新边界时$mid没加加减减,导致死循环或漏查。我的建议是这类基础算法必须练到闭着眼睛能写对的程度,毕竟在笔试环境下时间紧、心态急,平时手不稳的几乎必错。
括号匹配题则需要用栈来实现。PHP里没有内置栈结构,但数组的array_push()和array_pop()组合就是天然的栈。遇到左括号入栈,遇到右括号就出栈比对是否匹配,最后检查栈是否为空。这个题的考点不仅是栈的逻辑,还有“你能不能自然地用PHP的数组函数来实现栈操作”,这比在C++里手写链表要简单,但思路要清晰。
字符串反转题也有一个隐形坑:如果题目说的是“给定的字符串可能包含中文”,用strrev()这个函数会直接把UTF-8中文按字节反过来,得到乱码。如果题目没限制,笔试现场最简单的写法是implode('', array_reverse(str_split($str))),但遇到中文就得用mb_str_split。我当时跟学弟说,做题前先看一眼题目给的是ASCII字符串还是包含中文,这决定了你用什么函数,花半秒钟读题比写错重来划算得多。
5.2 Linux与运维命令:校招笔试里也能见真章
PHP工程师说到底还是要在Linux服务器上干活,所以卷子里也有一道Linux命令题,考的是几个基础命令的用法。
第一个是查看端口占用,netstat -tunlp | grep 80或者lsof -i:80,后者更简洁。第二个是查看最近日志和实时日志,tail -f /var/log/nginx/access.log。第三个是统计日志里每个IP的访问次数,经典管道组合:awk '{print $1}' access.log | sort | uniq -c | sort -rn。第四个是crontab定时任务,比如每天凌晨2点清一次缓存,写0 2 * * * php /path/to/clean_cache.php。
这些命令看着简单,但我面试过不少人,真能准确说出口的其实不多。笔试考这道题的逻辑是:你做PHP开发,不会少跟服务器打交道,连这些都不熟,上线排查问题会很痛苦。所以准备校招的时候,花两三天把常用的Linux命令过一遍,性价比很高。
5.3 业务设计题:高并发下你的方案能不能落地
卷子的最后一道题是业务设计,我记得是问“如果YY语音的某个热门直播间同时在线人数数十万,后端怎么设计才能保证消息能够实时发送到所有在线用户”。这道题欢聚时代的业务色彩很明显,需要从后端实时推送和前端拉取两个维度来答。
后端方面,直接方案是长连接,比如WebSocket或者TCP长连接,由网关层维护连接状态,消息通过Redis发布订阅或者消息队列广播到各网关节点,再由网关推送到客户端。不能所有客户端直接连业务服务器,因为每台服务器的连接数有限,需要做水平扩展。
前端方面,需要考虑消息的拉取兜底,比如WebSocket断线时用轮询或SSE补齐消息;消息要有序列号,客户端根据序列号补拉漏掉的消息。还要考虑消息内容太大时怎么拆分,弹幕量太大时怎么降级采样展示。能把这些实际工程里的问题答出来,说明你不是只会写CRUD,而是真正思考过大规模场景下系统会碰到什么问题。
6. 校招PHP笔试的实战策略与避坑经验
6.1 时间分配与答题顺序技巧
这套卷子题量不小,正常限时一个半小时左右。我的建议是拿到卷子先花两分钟扫一遍全部题目,标注出会做、不确定、完全不会三类。答题顺序上,先做有把握的填空和选择,再写简答题,算法题放中间,最后处理业务设计题。不要在一道题上卡超过十分钟,笔试的每一分都重要,宁可把会的题全部写出来,也不要为了钻牛角尖丢掉后面的基础分。
还有一个小技巧:手写代码时哪怕不能完整写出正确答案,也要把思路写出来。比如算法题可以写“先排序再遍历”“用双指针来处理”,简答题可以画结构图来表示流程。不要忽视“卷面分”这个东西,实际批卷时老师看的是你的解题思路是否合理,代码写得不完整但思路方向对,也能拿一半分。
6.2 手写代码的隐形扣分点
笔试手写代码和IDE里写代码完全不是一回事。没有语法高亮、没有自动补全、没有报错提示,任何一个分号写错都可能让整段代码直接挂掉。所以平时练习时一定要刻意在纸上或者纯文本编辑器里写代码,写完再复制到IDE里检查语法错误,这样才能逐步降低“手写代码低级出错率”这个隐形扣分点。
另一个隐形扣分点是变量命名和代码风格。有的人手写代码喜欢用$a、$b、$arr1这种毫无意义的命名,阅卷体验很差。同样的逻辑,用$userList、$index、$totalCount这样的命名,一打眼就能看出代码的意图。我会建议准备笔试的同学把代码整洁度当成基本功来练习,这会反映在每一个阅卷人的印象分里。
6.3 哪些题是校招笔试的高频“保分题”
根据这份卷子和当时其他互联网公司PHP校招的出题特点,有一些题属于高频“保分题”,一定要重点准备:PHP生命周期和PHP-FPM工作原理,POST和GET的区别与安全性,include和require的区别,SESSION和Cookie的差异,单例模式和工厂模式的实现,MySQL事务的ACID以及隔离级别,Redis常用数据类型和使用场景,常见排序算法的时间复杂度对比。这些题看起来基础,但每次笔试基本都会碰到,提前准备好,在考场上等于白捡分。
如果时间充裕,建议再把PHP官方文档里“语言参考”这一章节从头到尾刷一遍,重点看类型、变量、运算符、流程控制、类与对象、命名空间、错误处理这几块。这份文档是PHP最权威的学习资料,比市面上各种面试题总结靠谱多了。
7. 看到最后,聊点我的心里话
整理这份卷子解析的过程中,我一直在想一个问题:2017年的笔试题放到今天,还有参考价值吗?我的答案是:核心价值完全没有过时。PHP这门语言确实在发生变化,PHP 7的性能提升、PHP 8的JIT、注解、属性,这些新特性能让开发效率更高,但校招笔试考的是语言底层机制和编程思维的扎实程度,这些东西不会因为版本迭代就作废。弱类型比较的坑、索引失效的排查思路、单一职责的设计原则,无论你用TP还是用Laravel,无论你部署在云服务器还是容器里,该懂还是得懂。
我个人在这些年面试过不少PHP候选人,有一个很深的体会:基础扎实的人,学新框架、接新项目都非常快;基础薄弱的人,哪怕简历上写了会好几个框架,稍微换一个场景就露馅。像这份A卷里的题目,它其实是在用一种很朴素的方式提醒你:做技术这行,基本功永远是底气。
如果你正在准备校招,或者已经工作但想测试一下自己的PHP功底,试着在一个安静的环境里,用90分钟闭卷做一遍这套题。做完之后不要只看答案,重点分析每道错题背后的知识点,再回到官方文档里把对应的章节系统地过一遍。这样刷一遍题,比你漫无目的地刷100道面试题都要有效。祝你在接下来的笔试和面试里,每一分都拿得稳稳当当。