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 进程池可以配置:
  • static
  • dynamic
  • ondemand
  • 请求链路通常是:
  • 客户端 -> 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 installcomposer dump-autoload 时,Composer 会在 vendor/ 下面生成一组自动加载文件,例如:

  • vendor/autoload.php
  • vendor/composer/autoload_real.php
  • vendor/composer/autoload_psr4.php
  • vendor/composer/autoload_classmap.php
  • vendor/composer/autoload_static.php

本质上就是: Composer 先把你在 composer.json 里定义的规则整理成 PHP 代码和映射表,再注册到 SPL autoload 链上。

3. 运行层

当代码里第一次用到某个类时,例如:

$service = new \App\Services\OrderService();

Composer 注册的自动加载器会收到这个类名,然后按规则去找:

  1. 先看类名对应的命名空间前缀
  2. 根据 PSR-4 映射出目录
  3. 拼出文件路径
  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 和依赖管理成本过高的问题。

可以补的点

  • bind
  • singleton
  • 自动解析构造函数依赖
  • 服务提供者注册绑定关系

项目里怎么讲

像支付、归因、推送、搜索这类服务,如果通过容器管理,会比在业务逻辑里直接实例化更清晰,也方便扩展。


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 判断就说自己做了幂等,而是会把它拆成几层:

  1. 先定义幂等键
  • 支付回调用平台的 event_id / notify_id / refund_id
  • 创建订单接口可以要求客户端传 Idempotency-Key
  • 如果平台没有稳定事件 ID,就自己组装业务唯一键
  1. 数据库先落事件表,并加唯一索引
  • 常见做法是 provider + event_id 唯一约束
  • 插入失败就代表这个事件已经处理过,不再重复生效
  1. 主业务更新放在事务里
  • 锁订单或订阅行
  • 应用状态机
  • 更新支付状态、订阅状态、权益状态
  1. 副作用继续单独幂等
  • 账务流水
  • 会员开通
  • 优惠券 / 次数发放
  1. 分布式锁只做辅助
  • 它解决“同一时刻别并发改”
  • 真正防止“任意时间别重复生效”的主防线还是数据库唯一键和事务

你可以直接这样讲

我在 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 个点

  1. PHP-FPM 与 Nginx 协作
  2. Composer autoload
  3. interface / abstract / trait
  4. 依赖注入与服务容器
  5. Laravel 中间件
  6. session / cookie / token
  7. 设计模式与领域设计
  8. PHP 数组和内存
  9. 幂等、状态机与复杂业务拆层
  10. PHP 和 Go 的分工边界