如何使用CakePHP嵌套连接?

时间:2010-05-06 07:37:50

标签: php cakephp model join

我正在努力表现。因此,而不是使用以下SQL语法:

select *
from   tableA INNER JOIN
       tableB on tableA.id = tableB.tableA_id LEFT OUTER JOIN
       ( tableC INNER JOIN tableD on tableC.tableD_id = tableD.id)
       on tableC.tableA_id = tableA.id

我想使用CakePHP model->find()。这也允许我使用Paginator,因为据我所知,这不适用于自定义SQL查询(除非你将一个单一的分页查询硬编码到对我来说似乎有点不灵活的模型)。

到目前为止我尝试过:

/* inside tableA_controller.php, inside an action, e.g. "view" */
$this->paginate['recursive'] = -1; # suppress model associations for now
$this->paginate['joins'] = array(
    array(
        'table' => 'tableB',
        'alias' => 'TableB',
        'type'  => 'inner',
        'conditions' => 'TableB.tableA_id = TableA.id',
    ),
    array(
        'table' => 'tableC',
        'alias' => 'TableC',
        'type'  => 'left',
        'conditions' => 'TableC.tableA_id = TableA.id',
        'joins' = array( # this would be the obvious way to do it, but doesn't work
            array(
                'table' => 'tableD',
                'alias' => 'TableD',
                'type'  => 'inner',
                'conditions' => 'TableC.tableD_id = TableD.id'
            )
        )
    )
)

即,将连接嵌套到结构中。但这不起作用(CakePHP只是忽略嵌套的'joins'元素,这是我所期望的,但很难过。

我在使用语句构建器的注释中看到了关于如何进行子查询(在where子句中)的提示。可以在这里使用类似的技巧吗?

2 个答案:

答案 0 :(得分:2)

事实证明你做不到。至少不使用上面提供的语法而不是CakePHP 1.2.6。我查看了源代码(是的!开源框架!)并找到了包含连接代码的文件cake/libs/model/datasources/dbo_source.php

一切都从DboSource::renderStatement()开始,它对$query['joins']数组做了一个浅步,用DboSource::buildJoinStatement($join)的SQL片段替换了这些连接定义,这样可以整理一些参数(填充在空白等)然后调用DboSource::renderJoinStatement来创建单个连接子句的SQL片段。

  

me:这应该很容易解决!

我被告知不要在cake/libs中修改内容,因此我将文件dbo_source.php复制到app/models/datasources/进行编辑。然后我拿起我的斧头并重构了$query['joins']DboSource::renderStatement()数组的浅步行到一个新方法DboSource::buildJoinStatementArray(),产生了这两种方法:

function buildStatement($query, $model) {
    $query = array_merge(array('offset' => null, 'joins' => array()), $query);

    # refactored (extract method) to make recursion easier
    $query['joins'] = $this->buildJoinStatementArray($query['joins']);

    return $this->renderStatement('select', array(
        'conditions' => $this->conditions($query['conditions'], true, true, $model),
        'fields' => implode(', ', $query['fields']),
        'table' => $query['table'],
        'alias' => $this->alias . $this->name($query['alias']),
        'order' => $this->order($query['order']),
        'limit' => $this->limit($query['limit'], $query['offset']),
        'joins' => implode(' ', $query['joins']),
        'group' => $this->group($query['group'])
    ));
}
/**
 * Replaces the join statement array syntax with SQL join clauses.
 */
function buildJoinStatementArray($joins) {
    if (!empty($joins)) {
        $count = count($joins);
        for ($i = 0; $i < $count; $i++) {
            if (is_array($joins[$i])) {
                $joins[$i] = $this->buildJoinStatement($joins[$i]); # $joins[$i] now contains something like "LEFT JOIN users As User on User.group_id = Group.id"
            }
        }
    }
    return $joins;
}

我有DboSource::buildJoinStatementArray()后,是时候更改DboSource::buildJoinStatement()了 - 我所做的就是添加了对$data['joins']的检查以及该案例的替代呈现方法:

function buildJoinStatement($join) {
    $data = array_merge(array(
        'type' => null,
        'alias' => null,
        'table' => 'join_table',
        'conditions' => array()
    ), $join);

    if (!empty($data['alias'])) {
        $data['alias'] = $this->alias . $this->name($data['alias']);
    }
    if (!empty($data['conditions'])) {
        $data['conditions'] = trim($this->conditions($data['conditions'], true, false));
    }

    # allow for nested joins
    if (!empty($data['joins']) and is_array($data['joins'])) {
        $data['joins'] = $this->buildJoinStatementArray($data['joins']);
        return $this->renderNestedJoinStatement($data);
    }
    else
    {
        return $this->renderJoinStatement($data);
    }
}

新的renderNestedJoinStatement()方法非常类似于DboSource::renderJoinStatement()

/**
 * Renders a final SQL JOIN that contains nested join statements
 *
 * @param array $data
 * @return string
 */
function renderNestedJoinStatement($data) {
    extract($data);
    $nestedJoins = implode(' ', $joins);
    return trim("{$type} JOIN ({$table} {$alias} {$nestedJoins})ON ({$conditions})");
}

答案 1 :(得分:1)

如果我做对了,你就有了以下关系(希望在你的模特中):

TableA hasMany TableB.
TableA hasMany TableC.

TableB belongsTo TableA.

TableC belongsTo TableA.
TableC belongsTo TableD. (might be hasOne)

TableD hasMany TableC. (might be hasOne)

如果你正在使用Containable行为(我非常推荐它,并将其设置为app_model级别以便所有模型继承),我认为你可以做这样的事情......

$this->TableA->find(
  'all',
  array(
    'contain' => array(
      'TableB',
      'TableC' => array(
        'TableD'
      )
    ),
    'conditions' => array(...),
    'order' => array(...)
  )
);

如果您需要选择特定字段,那么您需要在contains参数中指定它们,例如在此我限制TableB的返回字段:

$this->TableA->find(
  'all',
  array(
    'contain' => array(
      'TableB' => array(
        'fields' => array(
          'field_1',
          'field_2'
        ),
      ),
      'TableC' => array(
        'TableD'
      )
    ),
    'conditions' => array(...),
    'order' => array(...)
  )
);

返回的数据应如下:

  [0] => array(
    [TableA] => array(
      [id] => 12,
      [name] => 'Foo'
    ),
    [TableB] => array(
      [id] => 23,
      [table_a_id] => 12,
      [name] => 'Bah'
    ),
    [TableC] => array(
      [id] => 45,
      [table_a_id] => 12,
      [table_d_id] => 67,
      [name] => 'Woo',
      [TableD] => array(
        [0] => array(
          [id] => 67,
          [table_a_id] => 12,
          [name] => 'Wah'
        )
      )
    )
  )

但是,我从来没有这样做,嵌套表是容器的父(TableD和TableC),所以它可能不起作用,但它可能值得一试。