You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

离开或关闭页面时如何触发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 10:09:47