PHP 八股题库
这份题库重点不是“纯语法”,而是面向中高级 PHP 后端岗位,尤其适合你这种长期做复杂业务系统的人。
一、PHP 面试里你要怎么定位自己
你最稳的说法是:
我的 PHP 经验不是停留在写接口,而是长期用 PHP 承接复杂业务系统,比如订阅、支付、退款、交易、后台、活动和多服务协同。
这句话很关键,因为它把 PHP 从“语言”拉到了“复杂业务承接能力”。
二、高频题
1. PHP-FPM 是什么,Nginx 和 PHP-FPM 是怎么协作的
标准回答
PHP-FPM 是 PHP 的 FastCGI 进程管理器,主要负责维护一组常驻的 PHP worker 进程。Nginx 收到动态请求后,会通过 FastCGI 协议把请求转发给 PHP-FPM,再由 PHP-FPM 分配一个空闲 worker 去执行 PHP 脚本,执行完成后把结果返回给 Nginx,再由 Nginx 返回给客户端。
更完整一点的原理
- PHP-FPM 里通常有 master 进程和一组 worker 进程
- master 负责监听端口 / socket、拉起和回收 worker、平滑重载配置
- worker 负责真正执行 PHP 脚本,一个 worker 同一时刻一般只处理一个请求
pm = static / dynamic / ondemand决定 worker 是固定常驻、按范围伸缩,还是按需拉起
你可以补的点
- Nginx 负责静态资源、连接管理和反向代理
- PHP-FPM 负责 PHP 脚本执行
- PHP-FPM 进程池可以配置:
staticdynamicondemand- 请求链路通常是:
客户端 -> Nginx -> FastCGI -> PHP-FPM -> worker 执行脚本 -> 返回 Nginx -> 返回客户端- FPM 打满的本质一般不是“参数太小”这么简单,而是请求执行速度和请求到达速度失衡
项目里怎么讲
你可以说:
像后台系统、交易链路、海外业务平台这类 PHP 服务,本质上都依赖 Nginx + PHP-FPM 这种模式。线上排查时,我会先看 FPM worker 数、慢请求、max_children 是否打满,再继续拆慢 SQL、外部依赖超时和热点请求,而不是只会调参数。
2. interface、abstract class、trait 的区别
标准回答
interface更强调规范,定义方法签名,不提供实现abstract class更强调抽象基类,可以有部分实现和属性trait更像代码复用工具,用于横向复用方法,解决单继承限制
面试更稳的说法
如果是业务约束,我更倾向用 interface; 如果是一组相关对象有公共状态和默认实现,我会用 abstract class; 如果只是复用一段公共能力,比如日志、格式化、公共工具方法,我会考虑 trait,但不会滥用。
3. include、require、include_once、require_once 区别
标准回答
include找不到文件会警告,但脚本继续执行require找不到文件会 fatal error,脚本中断_once会检查是否已经加载,避免重复引入
面试更稳的说法
现代项目里更多会走 Composer autoload,手写 include / require 的场景已经少很多,但理解它们对历史项目和底层行为还是有帮助。
4. Composer autoload 原理是什么
标准回答
Composer 会根据 composer.json 配置生成自动加载文件,常见是 PSR-4 映射。运行时通过 autoload 文件把命名空间映射到具体目录,需要类时再自动加载对应文件。
你可以补的点
- PSR-4 最常见
- 也支持 classmap、files 等
- 现代 PHP 项目基本离不开自动加载
如果要讲得更详细,可以这样拆
1. 配置层
项目会在 composer.json 里声明自动加载规则,例如:
{
"autoload": {
"psr-4": {
"App\\\\": "app/",
"Domain\\\\": "src/Domain/"
},
"classmap": [
"database/seeders",
"database/factories"
],
"files": [
"app/helpers.php"
]
}
}
这里的含义是:
psr-4:按命名空间前缀去找目录,是现代项目最常见的方式classmap:把类和文件直接做成一张映射表,适合不太符合 PSR-4 的目录files:项目启动时直接加载指定文件,常用来放全局辅助函数
2. 生成层
当你执行 composer install 或 composer dump-autoload 时,Composer 会在 vendor/ 下面生成一组自动加载文件,例如:
vendor/autoload.phpvendor/composer/autoload_real.phpvendor/composer/autoload_psr4.phpvendor/composer/autoload_classmap.phpvendor/composer/autoload_static.php
本质上就是: Composer 先把你在 composer.json 里定义的规则整理成 PHP 代码和映射表,再注册到 SPL autoload 链上。
3. 运行层
当代码里第一次用到某个类时,例如:
$service = new \App\Services\OrderService();
Composer 注册的自动加载器会收到这个类名,然后按规则去找:
- 先看类名对应的命名空间前缀
- 根据 PSR-4 映射出目录
- 拼出文件路径
- 如果文件存在就
require进去
所以自动加载不是“PHP 天生自己会找类”,而是 Composer 帮你提前生成并注册了一套查找规则。
4. Laravel 里具体怎么接进来
Laravel 请求入口一般从 public/index.php 开始。 这个入口很早就会先执行:
require __DIR__.'/../vendor/autoload.php';
这一步就是把 Composer 自动加载器先注册进去。 后面 Laravel 再去启动应用、加载服务提供者、解析控制器、调用中间件和业务类。
也就是说,Laravel 并不是自己发明了一套类自动加载,而是站在 Composer autoload 之上,把框架和业务代码整个串起来。
5. dump-autoload 是在干什么
- 新增类文件后,通常 PSR-4 路径规范的话,很多时候直接就能加载
- 如果改了
composer.json里的 autoload 配置,通常要重新执行composer dump-autoload - 线上常见会用
composer dump-autoload -o
这里的 -o 是优化 autoload,尽量把更多信息整理成更直接的映射,减少运行时查找成本。
面试里更稳的一段说法
Composer autoload 的本质,是把 composer.json 里的命名空间和目录规则,提前生成为 vendor 下的一套映射文件,并通过 SPL autoload 注册到运行时。Laravel 在 public/index.php 里先 require vendor/autoload.php,所以后面的控制器、服务类、事件、命令都能按命名空间自动加载。
项目里怎么讲
像 Laravel、Yii 这类项目,自动加载是基础能力。大型项目里统一命名空间和目录结构,不只是为了省掉 include,更重要的是让 app、domain、service、job、command 这些代码层次能稳定协作。
5. 什么是依赖注入,为什么有用
标准回答
依赖注入就是对象不自己 new 依赖,而是通过构造函数、方法参数或者容器把依赖传进来。好处是降低耦合、提升可测试性,也更方便做扩展和替换实现。
面试更稳的说法
依赖注入的核心不是“高级”,而是把对象创建和对象使用分开。复杂系统里,如果所有类都自己 new 依赖,后面很难维护和测试。
项目里怎么讲
你可以说:
在复杂业务系统里,像支付服务、归因服务、搜索服务这种外部依赖,我更倾向抽象成服务对象或接口,通过容器和依赖注入管理,而不是在业务逻辑里到处 new。
6. Laravel 服务容器解决什么问题
标准回答
Laravel 服务容器本质上是一个 IoC Container,用来管理对象创建、依赖解析和生命周期。它解决的是复杂系统里依赖关系越来越多时,手动 new 和依赖管理成本过高的问题。
可以补的点
bindsingleton- 自动解析构造函数依赖
- 服务提供者注册绑定关系
项目里怎么讲
像支付、归因、推送、搜索这类服务,如果通过容器管理,会比在业务逻辑里直接实例化更清晰,也方便扩展。
7. Laravel 中间件解决什么问题
标准回答
中间件主要用于请求进入控制器之前或响应返回之前做统一处理,比如鉴权、参数校验、限流、日志、跨域等。它把通用逻辑从控制器里抽出来,避免重复代码。
项目里怎么讲
我在项目里比较看重中间件层的职责边界,比如鉴权、灰度、日志、签名校验、基础风控这类逻辑,放中间件通常比塞在控制器里更合理。
8. session 和 cookie 区别
标准回答
- Cookie 存在客户端
- Session 服务端通常保存状态,客户端通过 Cookie 带 session_id 来关联
面试更稳的说法
Cookie 更适合少量客户端状态,Session 适合服务端会话管理。现代后端系统里,如果是 API 场景,很多时候也会用 token / JWT 替代传统 session。
9. 为什么现代后端 API 更倾向 token / JWT,而不是传统 session
标准回答
因为 API 服务往往是前后端分离、多端接入、甚至微服务化场景,token / JWT 更适合无状态认证和跨服务传递,而传统 session 更偏服务端会话模式。
你可以补的点
- JWT 不一定适合所有场景
- 如果要主动失效、踢登录、黑名单,就要补额外机制
10. 什么是面向对象设计里常见的设计模式
你最需要准备的不是死背 23 种,而是理解“模式到底在解决什么耦合问题”。
面试里更稳的讲法
设计模式的核心不是名词,而是把常见问题抽成稳定结构。 比如有些问题是“同一能力有多套实现”,那就适合策略模式; 有些问题是“状态变化要通知多个地方”,那就适合观察者; 有些问题是“复杂流程想给外部一个统一入口”,那就适合门面或编排层。
你至少要会讲这几种
- 工厂模式
- 单例模式
- 策略模式
- 责任链模式
- 模板方法
- 观察者模式
- 命令模式
- 门面 / 编排模式
项目里怎么讲
- 策略模式:不同支付渠道、不同回传平台、不同风控规则可以通过统一接口切换实现
- 工厂模式:不同支付渠道 / 不同平台服务封装成统一创建入口
- 责任链:审核流、规则过滤、事件处理链
- 观察者模式:状态变化后通知报表、日志、消息、前端展示
- 命令模式:把操作封装成清晰入口,比如后台命令、任务处理、事件消费入口
- 门面 / 编排模式:把支付、退款、权益同步、日志记录这些底层步骤包成一个统一流程
面试加分点
我理解设计模式不是为了显得高级,而是为了把变化点和稳定点拆开。比如同一能力要替换实现时,我会优先想到策略模式;如果一个复杂流程要给外部统一入口,我会考虑门面或编排层。
11. 什么是单例模式,它有什么问题
标准回答
单例模式保证一个类全局只有一个实例。好处是统一访问共享资源,但缺点是容易隐藏依赖、增加耦合、影响测试,不应该滥用。
面试更稳的说法
配置中心、连接池这类资源可能适合单例,但业务服务一般不建议到处单例化。
12. PHP 数组为什么强大,但也要小心
标准回答
PHP 数组底层是哈希表,同时兼容了 map 和 list 的特性,使用很方便,但代价是内存占用较高,不适合无脑拿大数组做大规模数据处理。
项目里怎么讲
做批量处理、导入、报表、历史数据清洗时,我会注意避免一次性把太多数据加载到内存里。
13. 如何理解 PHP 的垃圾回收
标准回答
PHP 主要通过引用计数回收内存,对循环引用再配合 GC 机制处理。大多数 Web 请求是短生命周期,脚本结束时内存会整体回收,但长任务或 Swoole 场景下更要注意内存泄漏。
14. 什么是幂等,你在 PHP 项目里怎么做
标准回答
幂等就是同一个请求重复执行多次,结果仍然一致。支付回调、订单创建、退款、消息消费这些场景都要重点考虑幂等。
PHP 项目里怎么落
我不会只写一个 if 判断就说自己做了幂等,而是会把它拆成几层:
- 先定义幂等键
- 支付回调用平台的
event_id / notify_id / refund_id - 创建订单接口可以要求客户端传
Idempotency-Key - 如果平台没有稳定事件 ID,就自己组装业务唯一键
- 数据库先落事件表,并加唯一索引
- 常见做法是
provider + event_id唯一约束 - 插入失败就代表这个事件已经处理过,不再重复生效
- 主业务更新放在事务里
- 锁订单或订阅行
- 应用状态机
- 更新支付状态、订阅状态、权益状态
- 副作用继续单独幂等
- 账务流水
- 会员开通
- 优惠券 / 次数发放
- 分布式锁只做辅助
- 它解决“同一时刻别并发改”
- 真正防止“任意时间别重复生效”的主防线还是数据库唯一键和事务
你可以直接这样讲
我在 PHP 项目里做幂等,核心不是单靠 Redis 锁,而是先定义事件唯一键,再通过数据库唯一约束、事务、状态机和副作用二次幂等把链路做稳。Redis 锁更多是辅助控制并发,主防线还是数据库。
面试官继续追问时你可以补
- 只用 Redis 锁不够,因为锁解决的是并发,不是历史重复
- 只做事件表去重也不够,因为积分、余额、权益这些副作用也可能重复发
- 幂等经常和状态机、事务边界、补偿任务一起讲,单独拆开讲会显得浅
15. 一个复杂 PHP 项目,如何避免业务逻辑越来越乱
标准回答
我会做几件事:
- 按业务域拆模块
- 控制控制器只做入口层
- 复杂逻辑下沉到 service / domain 层
- 外部依赖做封装
- 用状态机、策略模式和统一日志降低复杂度
更完整一点的回答
如果业务已经进入订阅、支付、退款、归因、活动、风控这类复杂状态系统,我会继续补几件事:
- 拆清主状态和派生状态,不要所有东西共用一个
status - 明确哪些规则属于订单、哪些属于订阅、哪些属于权益
- 用 DTO / VO / 枚举 / 状态机把关键语义固定下来
- 控制器只做参数接收、鉴权和返回,别把业务规则塞满控制器
- 关键链路统一做日志、幂等和补偿任务
- 当某块业务明显重于 CRUD 时,用领域设计的方式重新梳理边界
这个问题其实很适合你发挥
你可以说:
像订阅、支付、退款、归因这类复杂业务,如果都堆在 controller 里,后面一定会乱。我更习惯先把状态、服务边界和对外依赖拆清楚,再去写代码。
16. 什么是领域设计,什么时候适合在 PHP 项目里用
标准回答
领域设计通常就是 DDD,核心是让代码结构围着业务本身来组织,而不是先有一堆表和接口,最后再硬解释业务。 它特别适合规则复杂、状态多、边界难拆的系统。
你最该会讲的几个概念
- 实体:有身份标识,比如订单、订阅、用户
- 值对象:更强调值本身,比如金额、时间范围、状态快照
- 聚合:一组需要一起保持一致的对象边界
- 仓储:聚合的持久化入口
- 领域服务:不天然属于某个单一实体的业务规则
- 领域事件:某个关键业务事实发生后的对外通知
哪些场景适合上 DDD
- 支付、退款、订阅、权益同步
- 库存、履约、售后
- 审批流、营销规则、风控规则
- 多团队协作、同一个业务词汇经常被误解的系统
哪些场景不适合一上来就上 DDD
- 只是简单后台 CRUD
- 业务还没稳定,还在快速试错
- 团队还没有统一建模习惯,容易把 DDD 做成形式主义
面试里怎么说更稳
我理解领域设计不是为了堆术语,而是为了把复杂业务里的规则归属和一致性边界讲清楚。像支付、退款、恢复订阅、权益同步这种链路,如果没有领域边界,后面代码会越来越像一堆散落判断。
三、PHP 面试里很适合你主动讲的点
1. PHP 不是弱,只是场景不同
可以说:
我不觉得 PHP 适合所有场景,但在复杂业务规则、后台系统、需求变化快、协作密集的系统里,PHP 的落地效率和团队协作成本是很有优势的。
2. 复杂业务系统里的核心,不是写接口快
可以说:
我做 PHP 这些年最大的感受是,复杂系统里最重要的不是把接口写出来,而是状态、边界、异常场景和后续治理。
四、如果面试官继续追问
追问 1:你为什么现在还适合 PHP 岗
你可以答:
因为我过去几年主战场一直还是复杂业务系统,PHP 在这类业务里非常适合做快速且稳定的落地,我的经验优势也主要在这一层。
追问 2:PHP 和 Go 你怎么分工
你可以答:
PHP 我主要放复杂业务规则和后台能力,Go 我主要放实时链路、高并发处理和服务化部分。不是谁替代谁,而是按业务场景拆。
追问 3:PHP 做高并发是不是不合适
你可以答:
如果是极高并发、长链路、常驻服务,Go 会更适合;但很多高并发问题本质不只是语言问题,还包括缓存、异步、存储分层和链路设计。
五、你复习 PHP 最该背的 10 个点
- PHP-FPM 与 Nginx 协作
- Composer autoload
- interface / abstract / trait
- 依赖注入与服务容器
- Laravel 中间件
- session / cookie / token
- 设计模式与领域设计
- PHP 数组和内存
- 幂等、状态机与复杂业务拆层
- PHP 和 Go 的分工边界