Повторное использование QueryBuilder в доктрине DBAL


В следующем примере показан некоторый фрагмент примера кода. Вызов QueryBuilder DBAL доктрины выполняется там дважды - один раз для выполнения инструкции SELECT(*) и до этого для выполнения инструкции COUNT(*).

Общие настройки, такие как таблица, условия, порядок сортировки и ограничения результатов, применяются к повторно используемому объекту QueryBuilder.

Вопросы

  • Существуют ли недостатки неявного повторного использования $queryBuilder, как показано в примере?
  • Предлагается ли просто скопируйте-вставьте код для отдельных экземпляров QueryBuilder?
  • Являются ли побочные эффекты при использовании clone $queryBuilder?

Пример кода

/**
 * @param array $arguments
 * @return string
 */
private function getOutput(array $arguments)
{
    /** @var \Doctrine\DBAL\Connection $connection */
    $connection = $this->getConnection();

    $queryBuilder = $connection
        ->createQueryBuilder()
        ->from('some_table')
        ->orderBy('sorting')
        ->setMaxResults(100);

    $condition = $queryBuilder->expr()->andX();
    // ... build conditions
    $queryBuilder->where($condition);

    $count = $queryBuilder->select('COUNT(*)')->execute()->fetchColumn(0);
    if ($count === 0) {
        return 'There is nothing to show';
    }
    if ($count > 100) {
        $output = 'Showing first 100 results only:' . PHP_EOL;
    } else {
        $output = 'Showing all results:' . PHP_EOL;
    }

    // implicitly reusing previously defined settings
    // (table, where, orderBy & maxResults)
    $statement = $queryBuilder->select('*')->execute();
    foreach ($statement as $item) {
        $output .= $this->renderItem($item) . PHP_EOL;
    }

    return $output;
}
Author: Oliver Hader, 2016-10-16

1 answers

Конструктор запросов в Doctrine DBAL можно динамически использовать для определения запросов SQL, а также для повторного переопределения частей запроса. Таким образом, в общем случае вызов метода select() дважды в одном и том же экземпляре QueryBuilder переопределяет предыдущую часть запроса select. У построителя внутренне есть свойство для чистого или грязного состояния - как только состояние становится грязным, строка SQL должна быть воссоздана заново. Переопределение частей запроса, например, вызывает состояние "грязный".

Итак, в общем случае повторное использование QueryBuilder это возможно и прекрасно с простой технической точки зрения. Однако средство построения запросов не проверяет логику, специфичную для разных баз данных. Это означает, что лишние части запроса должны быть удалены вручную, чтобы избежать сбоев запроса при выполнении инструкции.

Лучший подход состоит в том, чтобы разделить процесс построения запроса на различные методы логического класса - один для назначения общих ограничений запроса и другие для конкретных контекстов (например, подсчет результатов по сравнению с упорядочением и ограничением результата набор). В конце концов, исходный пример кода может выглядеть следующим образом:

Для обогащения запроса введены два дополнительных метода

/**
 * @param QueryBuilder $queryBuilder
 */
private function addConstraints(QueryBuilder $queryBuilder)
{
    $condition = $queryBuilder->expr()->andX();
    // ... build conditions
    $queryBuilder->where($condition);
}

/**
 * @param QueryBuilder $queryBuilder
 */
private function addResultSettings(QueryBuilder $queryBuilder)
{
    $queryBuilder
        ->orderBy('sorting')
        ->setMaxResults(100);
}

Теперь есть два экземпляра QueryBuilder, однако запрос в основном определен в ранее показанных двух новых методах.

/**
 * @param array $arguments
 * @return string
 */
private function getOutput(array $arguments)
{
    /** @var \Doctrine\DBAL\Connection $connection */
    $connection = $this->getConnection();

    // first query builder instance for counting records
    $queryBuilder = $connection->createQueryBuilder()->from('some_table');
    $this->addConstraints($queryBuilder);
    $statement = $queryBuilder->select('COUNT(*)')->execute();

    $count = $statement->fetchColumn(0);
    if ($count === 0) {
        return 'There is nothing to show';
    }
    if ($count > 100) {
        $output = 'Showing first 100 results only:' . PHP_EOL;
    } else {
        $output = 'Showing all results:' . PHP_EOL;
    }

    // second query builder instance to actually retrieve result set
    $queryBuilder = $connection->createQueryBuilder()->from('some_table');
    $this->addConstraints($queryBuilder);
    $this->addResultSettings($queryBuilder);
    $statement = $queryBuilder->select('*')->execute();

    foreach ($statement as $item) {
        $output .= $this->renderItem($item) . PHP_EOL;
    }

    return $output;
}
 2
Author: Oliver Hader, 2016-12-29 22:39:24