PHP面试常见问题整理

发布时间:2026/7/15 2:52:17

PHP面试常见问题整理 swoole相关swoole与php-fpm的区别最直观的区别是长连接和短连接php-fpm每次访问要重新加载代码到内存中并且按照请求-响应单向通信swoole可以保持长连接并且通过进程的方式将代码常驻在内存中并且支持多线程、异步处理可以在游戏业务中担任socket服务能够轻松处理高并发请求。面试官问“你对Swoole框架的理解”怎么答你可以按照这个逻辑去“套路”面试官定义 “Swoole 是一套 C 语言编写的 PHP 异步网络通信引擎。它让 PHP 不再局限于 Web 领域而能胜任网络服务器、即时通信等高并发场景。核心优势背下来 “它最强大的地方在于协程 (Coroutine)。它能在同步编写代码的同时实现异步非阻塞的性能极大降低了开发高并发系统的难度。”连接 TP 经验 “虽然我之前主要用 TP但我了解到 TP6 也可以通过 think-swoole 扩展来运行。我理解在这种模式下我们需要注意单例模式的变量污染问题和连接池的使用。”几个Swoole术语如果面试官想考考你是否真的懂可能会抛出这几个词你只要听懂了就不慌协程 (Coroutine) 比线程更轻量。可以理解为在 PHP 内部模拟的多任务切换。连接池 (Connection Pool) 以前 TP 每次请求都连数据库。在 Swoole 里数据库连接是提前连好放在池子里的拿来即用速度极快。热更新 因为 Swoole 常驻内存修改代码后不会立即生效。通常需要重启 Worker 进程面试时可以提一下这一点显得你有实践经验。协程相关协程的优点首先是协程相对于进程更加轻量化并且协程由Swoole进行调度而进程由OS调度。通过协程可以异步的处理高并发的任务如果通过php-fpm进行大量的数据库操作它是以类似队列的方式进行每次处理一个操作在一个操作没有完成之后队列就一直堵塞 而协程是Swoole进行高效率调度的方式swoole通过在一个进程中开启多个协程异步处理任务当一个协程开启的时候CPU会转而处理其他的任务直到这个协程完成再回头处理。通过使用协程一个进程就能轻松处理成千上万个高频请求提高任务效率。伪代码use Swoole\Coroutine; // 同时开启两个协程 Coroutine\run(function () { Coroutine::go(function () { Coroutine::sleep(1); // 模拟 IO echo A 完成\n; }); Coroutine::go(function () { Coroutine::sleep(1); // 模拟 IO echo B 完成\n; }); }); // 结果总共只花了 1 秒多一点注意事项请注意协程容易产生数据污染的问题协程在同一个进程空间中进行因此需要避免使用容易混淆的全局变量但是可以通过Swoole\Coroutine::getContext()获取当前协程的上下文空间来使用上下文的局部变量来避免污染。另一方面禁止直接使用阻塞函数如果在协程中使用原生的阻塞函数sleep会失去携程的意义因为原生的阻塞函数是同步的如果需要使用阻塞函数应当通过Swoole\Runtime::enableCoroutine();开启协程化Swoole会将阻塞函数变成异步的。对数据库的操作应当使用连接池而非重新创建连接由于Swoole的协程是用于高频场景的因此对数据库的查询应当使用连接池即提前建立大量的数据库连接协程需要用到时拿来用用完归还如果重新创建连接会导致大量不必的开销。一句话协程是对并发处理能力的革命性提升。在游戏场景中比如玩家移动或释放技能会产生极其频繁的数据库或 Redis 读写。如果用传统的 FPM 模式进程会大量阻塞在 IO 等待上。而使用 Swoole 协程我们可以通过一键协程化让原生的数据库操作变为非阻塞。配合连接池技术我们可以在维持数万个 WebSocket 长连接的同时依然保持极低的响应延迟。这在处理用户基数巨大的游戏平台时是系统稳定性的核心保障。laravel相关1.服务容器这里Laravel与tp最大的不同在tp的习惯上通过use加载引入类或者通过助手函数调用相关的模块但是laravel几乎所有功能都是注册在容器里面的通过依赖注入只需要在类的构造函数中写明需要使用的类名laravel就会自动实例化把对象给你。把各种功能绑定到容器中是通过service provider2. eloquent orm的潜规则相比tp比较宽松的表个模型设计规则laravel对数据表的规定较为严格模型名可以是单位但是表名必须是复数并且必须有create_at和update_at的时间戳否则会报错。另一个是N1问题例如查询10个用户的头像用户表和头像表通过外键关联这时候如果先一次查10个用户再循环查头像需要执行11次查询但是通过with方法指定关联就可以只产生两次查询这就是预加载。3.四架马车中间件、模型迁移、命令行、模板主要就是中间件模型迁移、命令行、blade模版。模板blade模板最核心的一点是他会被编译成原生的php缓存这样能够得到最大的性能。同时还支持布局继承即通过yield在父模板中定义插槽在子模版通过extend继承父模板并且通过section 指定在父模板中留空的部分。同时还可以通过php artisan make:component xxx 定义组件定义的组件存放在app\View\Components\xxx对应视图resources\view\components\xxx在组件的构造函数指定组件的参数并且在引用组件的时候通过x-组件名 :参数值 /模板还支持通过auth与endauth来鉴权和通过guest与endguest判断是否为游客。在foreach与endforeach进行循环可以通过$loop-first和$loop-last判断头尾。中间件关于中间件就是在每次请求在执行实际的业务代码进行响应之前先执行中间件来进行一些必要的检查例如鉴权、日志、处理跨域等。也可以在业务代码执行之后在响应之前再通过中间件进行一些操作。中间件分为全局中间件和路由中间件全局中间件在app\http\Kernel中登记之后全局生效路由中间件在路由定义的时候通过-middleware指定使用的中间件可以通过 php artisan make:middleware来快速创建一个中间件在创建的中间件中在handle方法中编写业务逻辑一般是先写前置操作然后需要通过 $response next(next(next(request); 来结束前置运行将请求传递给控制器在$resposne 后编写的代码会在控制器响应之后作为后置操作来执行。一般可以用中间件完成登录鉴权、请求频率限制、数据格式化、日志追踪、参数加解密等。命令行关于命令行类似django和thinkphp的命令行laravel的命令行被称为artisan 对应tp的 php think命令可以用来自动生成代码、管理数据库、运行计划任务、调试系统等。主要语法是 php artisan [command] [option] [argument]基础创建php artisan make:controller 创建控制器php artisan make:model User 创建模型php artisan make:middleware xxx 创建中间件php artisan make:migration create_order_table 创建迁移迁移操作laravel的数据库管理非常依赖迁移通过迁移工具即可在命令行中同步代码与数据库描述的结构。php artisan migrate 将代码中描述的结构同步到数据库php artisan migrate:rollback 撤销最后一次操作php artisan migrate:refresh 清空并创建表php artisan db:seed 生成测试用的假数据应用管理与调试php artisan route:list 列出所有路由 可以用来检查路由是否挂载成功php artisan config:cache / php artisan cache:clear 清空缓存例如修改了.env文件要用php artisan tinker 进入一个php的交互shell来调试php代码。自定义命令通过php artisan make:command xxx 可以用于创建自定义命令行程序在生成的代app/Console/Commands/xxx.php 来编辑命令行程序需要修改$signature 来定义命令名 例如名称为game:check 然后再handle方法中编写具体实现最后通过php artisan game:check来执行命令行程序。计划任务原先我们通过corntab来管理计划任务laravel将这个管理收回到了框架代码中只需要执行一次php artisan schdule:run 即可通过laravel自动管理计划任务在 app\console\kernel.php中编写 $schedule-command(命令)-周期即可挂载计划任务。如何管理数据库变更或者定时任务在我的开发流程中我严格遵守 Laravel 的 Migration 规范。所有数据库表结构的变更都通过 php artisan make:migration 生成这样可以确保团队成员之间的数据结构高度同步也方便在部署时一键更新。对于定时任务我不直接修改系统的 Crontab而是利用 Laravel 的 Task Scheduler。在 Console/Kernel 中统一定义任务计划。这样做的核心好处是任务逻辑随代码版本走不需要去服务器上手动配置运维起来非常优雅。”迁移1. 迁移的本质数据库的“Git”你可以把每一个迁移文件看作是 Git 的一个Commit。Git管理的是代码的增删改。Migration管理的是数据库表结构的增删改。为什么需要它在团队开发中如果你的同事建了一个表他不需要发给你一个.sql文件你只需要执行git pull拿到迁移文件然后运行php artisan migrate你的数据库就和他的一模一样了。2. 迁移文件的结构解析一个典型的迁移文件由php artisan make:migration生成包含两个核心方法up()执行变更当你运行php artisan migrate时系统会执行这个方法。PHPpublic function up() { Schema::create(orders, function (Blueprint $table) { $table-id(); // 主键 $table-foreignId(user_id); // 外键 $table-decimal(amount, 8, 2); // 金额 $table-timestamps(); // 自动生成 created_at 和 updated_at }); }down()回滚变更当你运行php artisan migrate:rollback时系统会执行这个方法撤销up做的事情。PHPpublic function down() { Schema::dropIfExists(orders); // 删掉这个表 }3. 核心命令全家桶 (面试必记)在面试中面试官可能会问你如何处理不同场景下的数据库变更你需要熟练说出这些命令php artisan migrate运行所有尚未执行的迁移。php artisan migrate:rollback回滚最后一次迁移操作回滚上一个 Batch。php artisan migrate:rollback --step5回滚最后的 5 个迁移文件。php artisan migrate:reset回滚数据库中所有的迁移相当于把表全删了。php artisan migrate:refresh先重置reset再重新执行migrate。常用语开发阶段重新洗牌。php artisan migrate:fresh最暴力直接把数据库里的表全删了不管是不是迁移生成的然后从头开始跑。4. 迁移状态表migrations你可能会好奇Laravel 怎么知道哪些文件跑过了 你在宝塔里打开数据库会发现多了一张migrations表。它只有两个关键字段migration: 文件名。batch: 批次号。原理每次你运行migrate命令Laravel 都会把跑过的文件名存进去。下一次运行时它会对比文件夹里的文件名和表里的记录只跑表里没有的文件。5. 生产环境的坑“生产环境下执行迁移有什么需要注意的吗”这是区分初级和中级开发者的关键点数据丢失风险永远不要在生产环境运行migrate:fresh或migrate:refresh。超时与锁表如果表里有几百万条游戏日志执行addColumn可能会导致长时间锁表。应对建议提到你会用Online DDL或者是选择在凌晨业务低峰期操作。原子性尽量保证一个迁移文件只做一件事比如只创建一个表而不是同时修改三个表。如果中间出错了方便回滚。先备份后迁移在执行重大变更前先用mysqldump备份数据库是职场生存的基本准则。Composer相关composer是什么对于 PHP 开发者来说Composer不是一个可选工具它是生存工具。Laravel 的每一个核心组件、每一个插件都是通过 Composer 管理的。Composer类似npm和pip等包管理工具在PHP和Laravel中composer可以用来管理项目依赖。composer.lock与composer.json这是composer的两个核心配置文件.json描述当前项目使用的依赖与大致版本以及项目相关的信息等用于给开发者提供参考优先级上来说.json文件的优先级不如.lock文件。composer.lock(版本锁)记录了当前项目实际安装的每一个包的精确版本号它是给机器看的。关键点团队开发中.lock文件必须提交到 Git。这样能保证你和同事、以及线上宝塔服务器安装的包版本完全一致避免“我本地没问题线上崩了”的情况。常用命令install 与 updateinstall优先读取.lock锁文件来安装对应的依赖如果有.lock文件他会忽略.json文件而update会读取json文件进行依赖安装并且更新.lock文件在生产环境不要乱用这个。composer requirecomposer require命令用于安装新的包并且修改composer.json与composer.lock文件比手动改文件要安全composer dump-autoload用于在手动修改了composer.json文件的情况下清理缓存。自动加载原理在composer.json中有一个autoload的配置项例如autoload: { psr-4: { App\\: app/, Database\\Factories\\: database/factories/, Database\\Seeders\\: database/seeders/ } },他遵循psr-4规范通过维护一个类名路径的字典来决定如何自动加载当使用use 类名来引用类的时候composer的autoLoader会通过这个来寻找文件性能优化与镜像源如果是线上部署项目可以通过compsoer install --optimize-autoloader --no-dev表示启用自动加载优化会将自动加载的动态表转为静态表增加查找速度并且通过--no-dev参数指定排除开发测试使用包例如单元测试等。在国内环境加速和避免网络异常的问题应该使用composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/更换为阿里镜像源。数据库优化相关B树与B树B树是一种多路平衡查找树可以理解为一种升级版的二叉树。B树的每一个节点又存数据又存索引这会导致能够存储的索引数量有限而B树的非叶子节点只存索引不存数据这样树会非常的扁平能够存储更多的索引来快速查找并且B树的叶子节点中又有双向循环链表链接因此又能更好的提升查找性能。相对于B 树B树存放的索引更多在查询的时候进行的磁盘IO更少因此速度更快并且查询性能更加稳定范围查询极强只需要找到第一个然后顺着链表一直找。为什么我们的主键通常建议用自增 ID而不是 UUID随机字符串这和 B 树的结构有关。B 树的叶子节点是按顺序排列的。如果使用自增 ID每次新插入数据都是在末尾追加树的结构非常稳定索引维护开销小。如果使用UUID这种随机字符串新数据可能会插入到树的中间位置导致频繁的**页分裂Page Split**和数据移动。这会产生大量的磁盘 IO 和碎片严重拉低数据库的写入性能。

相关新闻