如何正确保留 Doctrine 的查询实例


How to properly persist a Doctrine's query instance?

在Web工作流中,我需要将查询从请求传递到另一个占用的PHP会话。

不幸的是,我无法传递教义的查询,因为它包含的资源,这是不可分割的。

目前,我将 Doctrine 的查询对象转换为 SQL 字符串对象,并将其持久化到带有要绑定的参数的会话中。但是使用这种方法,当我从会话中获取查询时,我无法添加SQL条件(我的查询末尾可以有ORDER BYGROUP BY或其他语句......

如果我能成功取回 Doctrine 的查询对象,它解决了我的问题,当使用查询生成器时,语句的顺序并不重要。

你知道绕过这个问题的方法吗?

我想坚持的查询示例:

$query = $this->app['db']->createQueryBuilder();
$query->select('e.n AS Name'
        , 'SUM(nb) AS Nombre'
        , 'ROUND(SUM(CASE WHEN data = 2 THEN nb END) * CAST(100 AS float) / SUM(nb), 2) AS [Data type 2]')
    ->from('myDb.dbo.tableA', 'i')
    ->leftJoin('i', 'myDb.dbo.tableB', 'e', 'e.id = i.id')
    ->where("ind = :ind")->setParameter('ind', $this->ind)
    ->andWhere("(DATEPART(wk, date) = DATEPART(wk, GETDATE()) AND YEAR(date) = YEAR(GETDATE()))")
    ->andWhere("e.country= :country")->setParameter('country', 'UK')
    ->groupBy("e.n")
    ->orderBy("e.n");
if(!$app['isAdmin'])
    $query->andWhere("e.userPermission = :userPermission")->setParameter('userPermission', $app['user']);
$qb = $this->app['session']->set('query', $query);

我使用Symfony的会话组件。

正如注释中所暗示的,您可以将$query上的方法调用链转换为可序列化的内容。其他语言具有这种称为代数数据类型的语言结构,它允许您在类型中精确地编码一组替代项(即可能的值)(C 中存在一种称为 enum 的更简单结构)。这在PHP中不存在,但它可以用常量来模拟。

// original code
$query = $this->app['db']->createQueryBuilder(); 
$query->select('e.n AS Name' , 'SUM(nb) AS Nombre' , 'ROUND(SUM(CASE WHEN data = 2 THEN nb END) * CAST(100 AS float) / SUM(nb), 2) AS [Data type 2]')
      ->from('myDb.dbo.tableA', 'i')
      ->leftJoin('i', 'myDb.dbo.tableB', 'e', 'e.id = i.id')
      ->where("ind = :ind")
      ->setParameter('ind', $this->ind)
      ->andWhere("(DATEPART(wk, date) = DATEPART(wk, GETDATE()) AND YEAR(date) = YEAR(GETDATE()))")
      ->andWhere("e.country= :country")
      ->setParameter('country', 'UK')
      ->groupBy("e.n")
      ->orderBy("e.n"); 
if(!$app['isAdmin'])
  $query->andWhere("e.userPermission = :userPermission")
        ->setParameter('userPermission', $app['user']);
$qb = $this->app['session']->set('query', $query);

// algebraic representation, which is serializable

$query = [
   "default" => [
      "select"       => ['e.n AS Name' , 'SUM(nb) AS Nombre' , 'ROUND(SUM(CASE WHEN data = 2 THEN nb END) * CAST(100 AS float) / SUM(nb), 2) AS [Data type 2]'],
      "from"         => ['myDb.dbo.tableA', 'i'],
      "leftJoin"     => ['i', 'myDb.dbo.tableB', 'e', 'e.id = i.id'],
      "where"        => ["ind = :ind"],
      "andWhere"     => [
          ["(DATEPART(wk, date) = DATEPART(wk, GETDATE()) AND YEAR(date) = YEAR(GETDATE()))"],
          ["e.country= :country"]
      ],
      "groupBy"      => ["e.n"],
      "orderBy"      => ["e.n"]
   ],
   "tuning"   => [
      "not_admin"    => [
          "andWhere"     => ["e.userPermission = :userPermission"],
      ]
   ]
];
使用string值作为

标签,使用数组作为参数,我们实际上最终会得到一些相当可读的东西。

现在,要将其转回原始调用链,我们只需要使用 call_user_function_array .

// executing the above
function chaincall($query, $method, $params) {
    if (is_array($params[0]))
        foreach ($params as $call)
            call_user_func_array([$query,$method], $call);
    else
        call_user_func_array([$query,$method], $params);
}

$query = $this->app['db']->createQueryBuilder();
// baseline query
foreach ($query_data['default'] as $meth => $params) {
    chaincall($meth, $params);
}
// non admin user query tuning
if(!$app['isAdmin'] && isset($query_data['tuning']['not_admin']){
foreach ($query_data['not_admin'] as $meth => $params) {
    chaincall($meth, $params);
}
// other kind of query tuning?
// ...
// parameters
$query->setParameter('ind', $this->ind)
      ->setParameter('userPermission', $app['user']);

我试图通过根据可序列化值中的应用程序状态指定查询"tuning"1 来遵循原始代码的逻辑,但您可以根据需要轻松提取或重构该代码。

这段代码非常简单,并利用了PHP的灵活性,但它并不安全。我建议将方法名称字符串文字(例如"select","andWhere")替换为实际常量(这是枚举部分),并使用数组来检查该方法是否获得授权,或者用仅导出这些方法的对象包装查询对象。同样,我可能会根据 DQL 语法将查询片段进一步分解为尽可能小的元素(原子),但这会使执行部分合理地复杂化。


1:因为缺乏更好的术语。