离开或关闭页面时如何触发Doctrine执行数据库删除操作?
日程编辑页自动释放锁功能实现问题修复
场景说明
正在开发基于Symfony、Doctrine、JavaScript的日程规划应用,当前开发日程修改页面,项目包含Twig模板、PHP控制器、JavaScript脚本三类代码文件。
核心业务规则:用户进入某日期的日程修改页时,系统会向数据库写入对应锁记录,避免两名用户同时修改同一天日程;需要实现用户离开修改页面(包括突然关闭浏览器场景)时,自动删除数据库中对应的锁记录,释放该日期的修改权限供其他用户使用。
原有代码与配置
PHP控制器代码
public function modificationDeleteTest(){ $modificationRepository = new ModificationRepository($this->getDoctrine()); $modifications = $modificationRepository->findAll(); // I'm using GET and POST just to be sure to get the date somehow if (isset($_GET['date'])) { $dateModified = $_GET["date"]; } if (isset($_POST['date'])) { $dateModified = $_GET["date"]; } $i = 0; foreach ($modifications as $modification) { $modifArray[] = array( 'dateModified' => ($modification->getDatemodified()->format('Y-m-d')) ); if($modifArray[$i]['dateModified']==$dateModified){ $this->modificationDelete($modificationRepository, $modification); } $i++; } }
前端JavaScript代码
window.onbeforeunload = function() { if(isset($_GET("date"))){ dateModified = $_GET("date"); } if (isset($_GET("id"))) { idUser = $_GET("id"); } $.ajax({ url: "/ModificationDeleteTest", type: 'POST', data: {date:dateModified, id:idUser}, async:false, // so browser waits till xhr completed success:function() { alert("bye!"); } }); };
路由配置
ModificationDeleteTest: path: /ModificationPlanning controller: App\Controller\ModificationPlanningController::modificationDeleteTest methods: [POST]
问题根因
- 前端请求地址和路由配置不匹配,JS写的请求路径是
/ModificationDeleteTest,实际后端路由路径是/ModificationPlanning,请求根本没打到对应控制器 - JS代码混用PHP语法,
isset()、$_GET都是PHP专属语法,前端运行直接报语法错误,事件函数完全不执行 - PHP控制器逻辑不符合Symfony框架规范:直接实例化Repository没有注入依赖、POST参数取值错写为$_GET、遍历全表查锁效率极低、删除操作后没有调用flush持久化、没有返回规范响应
- 用同步Ajax在页面卸载时发请求,现代浏览器会拦截这类阻塞页面关闭的请求,可靠性极差
修复方案
1. 修正控制器逻辑
按照Symfony规范改写控制器方法,通过依赖注入获取实体管理器,直接查询目标锁记录,删除后持久化到数据库,返回标准JSON响应:
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\JsonResponse; use App\Entity\Modification; public function modificationDeleteTest(Request $request): JsonResponse { $entityManager = $this->getDoctrine()->getManager(); $modificationRepository = $entityManager->getRepository(Modification::class); // 统一从Request对象取请求参数,无需区分GET/POST $dateModified = $request->get('date'); $userId = $request->get('id'); // 直接查询当前用户对应当前日期的锁记录,避免全表遍历 $lockRecord = $modificationRepository->findOneBy([ 'datemodified' => new \DateTime($dateModified), 'user' => $userId // 替换为你实体中实际的用户关联字段 ]); if ($lockRecord) { $entityManager->remove($lockRecord); $entityManager->flush(); } return new JsonResponse(['code' => 0, 'msg' => 'lock released']); }
2. 修正前端请求逻辑
- 不要在JS里解析URL参数,直接在Twig模板渲染时把当前日期、登录用户ID输出为JS变量
- 用浏览器原生
navigator.sendBeacon()替代同步Ajax发卸载时的请求,这个API专门为页面卸载场景设计,不会被浏览器拦截,请求可靠性远高于同步Ajax - 删除unload事件里的alert弹窗,这类弹窗在页面卸载时会被浏览器强制拦截
Twig模板中提前注入变量:
<script> // 模板渲染时直接注入后端变量,无需前端解析 const CURRENT_EDIT_DATE = '{{ editDate|date('Y-m-d') }}'; // 替换为你传给模板的日期变量名 const CURRENT_USER_ID = {{ app.user.id }}; </script>
页面卸载事件逻辑:
window.addEventListener('beforeunload', () => { const submitData = new FormData(); submitData.append('date', CURRENT_EDIT_DATE); submitData.append('id', CURRENT_USER_ID); // 请求地址和后端路由路径保持一致 navigator.sendBeacon('/ModificationPlanning', submitData); });
3. 增加兜底机制
不要完全依赖前端触发解锁逻辑,给锁表增加expire_at过期时间字段,创建锁时默认设置15-30分钟后过期,配置定时任务定期清理过期锁,避免浏览器崩溃、网络中断导致锁永久占用。
内容的提问来源于stack exchange,提问作者florian bethencourt
相关产品推荐
相关产品推荐

