如何正确持久化Doctrine的查询实例?

时间:2015-01-12 10:57:38

标签: php symfony doctrine-orm query-builder

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

不幸的是,我无法通过一个Doctrine的查询,因为它包含一个不可串行化的资源。

目前我将我的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的会话组件。

1 个答案:

答案 0 :(得分:1)

正如评论中暗示的那样,您可以将$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']);

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

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


1:缺乏更好的术语。