Как отладить запрос в extbase?

$query = $this->createQuery();

    return $query->matching($query->like('linker', "$linkerKey=$linkerValue"))
        ->setOrderings(array('crdate' => $ordering))
        ->execute();

Как я могу отладить такой сгенерированный запрос в extbase? При повторном создании того же запроса (но без execute()) и попытке отобразить его с помощью var_dump или внутреннего t3lib_div::debug я просто получаю пустую страницу.


person pdu    schedule 22.02.2011    source источник


Ответы (9)


В версии 8.7 LTS необходимо использовать другой способ:

$queryParser = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class);
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($queryParser->convertQueryToDoctrineQueryBuilder($query)->getSQL());
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($queryParser->convertQueryToDoctrineQueryBuilder($query)->getParameters());
person pgampe    schedule 31.05.2017
comment
Отлично! Это действительно помогает найти то, что отстает. - person Mihir Bhatt; 26.10.2017
comment
Почему это не встроено напрямую в DebuggerUtility?! - person Blcknx; 28.12.2018
comment
Спасибо. Эти строки будут для меня святы. - person Paul Beck; 19.02.2019
comment
Я думаю, что исполняемый оператор SQL необходим. $queryParser = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class); $sql = $queryParser->convertQueryToDoctrineQueryBuilder($query)->getSQL(); $paramters = $queryParser->convertQueryToDoctrineQueryBuilder($query)->getParameters(); $search = array(); $replace = array(); foreach ($paramters as $k => $v) { $search[] = ':' . $k; $replace[] = '\'' . $v . '\''; } $sql = str_replace($search, $replace, $sql); \TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($sql); - person Franz Holzinger; 07.12.2019
comment
pgampe, @FranzHolzinger, на основе ваших ответов я создал абстрактный репозиторий, как показано в другом ответе, меня интересует ваш мнение и, возможно, исправления и тестирование в предыдущих версиях TYPO3. - person biesior; 03.08.2020

Эта информация устарела и не рекомендуется в TYPO3 8.7, и я оставляю ответ только для справки. Обратитесь к ответу @pgampe о том, как отлаживать запросы extbase в более поздних версиях TPYO3.

Extbase теперь имеет для этого QueryParser. В вашем методе репозитория прямо перед возвратом выполненного запроса вставьте:

    $parser = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Storage\\Typo3DbQueryParser');  
    $queryParts = $parser->parseQuery($query); 
    \TYPO3\CMS\Core\Utility\DebugUtility::debug($queryParts, 'query');

Результатом является табличное представление частей запроса, разделенных ключевыми словами SQL, например:

табличное представление рассматриваемого запроса

Имейте в виду, что результат QueryResult, возвращаемый вашим репозиторием, может отличаться от результата запроса SQL. Extbase использует PropertyMapper, чтобы попытаться преобразовать каждую результирующую строку в ExtbaseObject. Если PropertyMapper настроен неправильно или строка содержит данные, которые не могут быть преобразованы в типы данных в соответствии с конфигурацией, эти объекты будут автоматически пропущены.

person j4k3    schedule 20.05.2015
comment
В версии 8.7 это дает мне ошибку: Call to undefined method TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::parseQuery() - person nHaskins; 22.06.2017
comment
Как указал @pgampe, этот метод устарел. Смотрите его ответ для обновления. - person j4k3; 22.06.2017
comment
Хорошо, теперь отладьте запрос, сгенерированный в одном из основных файлов, например: $this->xyRepository->add($xyObject); - person Florian Rachor; 27.06.2018

Этот хак для extbase грязный, но полезный:

В typo3/sysext/extbase/Classes/Persistence/Storage/Typo3DbBackend.php отредактируйте метод buildQuery(array $sql) перед оператором return, добавьте:

t3lib_div::debug($statement, 'SQL Query Extbase');

Удалите после использования, и не забывайте, что это повлияет на все, что работает в extbase, поэтому используйте только в среде разработки.

Источник: http://sancer-media.net/2011/extbase-schneller-mysql-debug.html

person Urs    schedule 21.06.2013
comment
Спасибо, что опубликовали свой ответ! Обратите внимание, что вы должны опубликовать основные части ответа здесь, на этом сайте, иначе ваше сообщение может быть удалено см. FAQ, где упоминаются ответы, которые являются «чуть больше, чем ссылкой». Вы все равно можете включить ссылку, если хотите, но только как «ссылку». Ответ должен стоять сам по себе, без ссылки. - person Taryn; 22.06.2013
comment
Обратите внимание, что в TYPO3 6.1 это находится по адресу typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php, но хак больше не работает. - person Urs; 04.12.2013
comment
См. ответ @biesior в stackoverflow.com/questions /13084863/ для 6.1 - работает - person Urs; 04.12.2013

Это работает до тех пор, пока поддерживается $GLOBALS['TYPO3_DB']. Он покажет вам полный SQL-запрос сборки.

/**
 * @param \TYPO3\CMS\Extbase\Persistence\QueryResultInterface $queryResult
 * @param bool $explainOutput
 * @return void
 */
public function debugQuery(
    \TYPO3\CMS\Extbase\Persistence\QueryResultInterface $queryResult,
    $explainOutput = false
) {
    $GLOBALS['TYPO3_DB']->debugOuput = 2;
    if ($explainOutput) {
        $GLOBALS['TYPO3_DB']->explainOutput = true;
    }
    $GLOBALS['TYPO3_DB']->store_lastBuiltQuery = true;
    $queryResult->toArray();
    \TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump(
        $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery
    );
    $GLOBALS['TYPO3_DB']->store_lastBuiltQuery = false;
    $GLOBALS['TYPO3_DB']->explainOutput = false;
    $GLOBALS['TYPO3_DB']->debugOuput = false;
}

Итак, с помощью этой функции вы можете сделать что-то подобное в своем контроллере:

$all = $this->repository->findAll();
$this->repository->debugQuery($all);
person simplychrislike    schedule 17.01.2018

Согласно ответу @pgampe и @FranzHolzinger, комментарий Я создал AbstractonRepository в своем ext с некоторыми извлеченными методами, которые можно расширить в вашем собственном репозитории, чтобы сделать возможность отладки немного более удобной.

ПРИМЕЧАНИЕ Это протестировано и работает для TYPO3 версии 10.x, скорее всего, будет работать и с 8.7+, требует тестирования.

<?php

namespace VENDOR\Extkey\Domain\Repository;

use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
use TYPO3\CMS\Core\Exception;
use TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;


/**
 * Class AbstractRepository brings methods for query debugging in TYPO3 ver. 10.x
 * Based on StackOverflow answer by `pgampe` answer and `FranzHolzinger` comment
 * source https://stackoverflow.com/a/44286155/1066240
 *
 * All repositories in this extension should extend it.
 *
 * @author Marcus Biesioroff <[email protected]>
 * @package VENDOR\Extkey\Domain\Repository
 */
abstract class AbstractRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{


    /**
     * @param mixed       $query TYPO3\CMS\Core\Database\Query\QueryBuilder | TYPO3\CMS\Extbase\Persistence\Generic\Query
     * @param string|null $title Optional title for var_dump()
     * @param bool        $replaceParams if true replaces the params in SQL statement with values, otherwise dumps the array of params. @see self::renderDebug()
     *
     * @throws Exception
     */
    protected function debugQuery($query, string $title = null, bool $replaceParams = true): void
    {
        if ($query instanceof \TYPO3\CMS\Core\Database\Query\QueryBuilder) {
            $sql = $query->getSQL();
            $params = $query->getParameters();
            $this->renderDebug($sql, $params, $title, $replaceParams);
        } elseif ($query instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Query) {
            $this->parseTheQuery($query, $title, $replaceParams);
        } else {
            throw new Exception('Unhandled type for SQL query, curently only TYPO3\CMS\Core\Database\Query\QueryBuilder | TYPO3\CMS\Extbase\Persistence\Generic\Query can be debugged with ' . static::getRepositoryClassName() . '::debugQuery() method.', 1596458998);
        }
    }

    /**
     * Parses query and displays debug
     *
     * @param QueryInterface $query Query
     * @param string|null    $title Optional title
     * @param bool           $replaceParams if true replaces the params in SQL statement with values, otherwise dumps the array of params. @see self::renderDebug()
     */
    private function parseTheQuery(QueryInterface $query, string $title = null, $replaceParams = true): void
    {
        /** @var Typo3DbQueryParser $queryParser */
        $queryParser = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class);

        $sql = $queryParser->convertQueryToDoctrineQueryBuilder($query)->getSQL();
        $params = $queryParser->convertQueryToDoctrineQueryBuilder($query)->getParameters();
        $this->renderDebug($sql, $params, $title, $replaceParams);

    }


    /**
     * Renders the output with DebuggerUtility::var_dump()
     *
     * @param string      $sql Generated SQL
     * @param array       $params Params' array
     * @param string|null $title Optional title for var_dump()
     * @param bool        $replaceParams if true replaces the params in SQL statement with values, otherwise dumps the array of params.
     */
    private function renderDebug(string $sql, array $params, string $title = null, bool $replaceParams = true): void
    {
        if ($replaceParams) {

            $search = array();
            $replace = array();
            foreach ($params as $k => $v) {
                $search[] = ':' . $k;
                $type = gettype($v);
                if (in_array($type, ['integer'])) {
                    $replace[] = $v;
                } else {
                    $replace[] = '\'' . $v . '\'';
                }
            }
            $sql = str_replace($search, $replace, $sql);
            DebuggerUtility::var_dump($sql, $title);
        } else {
            DebuggerUtility::var_dump(
                [
                    'SQL'        => $sql,
                    'Parameters' => $params
                ],
                $title);
        }
    }
}

Его можно использовать в вашем репозитории следующим образом:

<?php

namespace VENDOR\Extkey\Domain\Repository;


use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;


class FooRepository extends \VENDOR\Extkey\Domain\Repository\AbstractRepository
{

    public function findByName($name)
    {
        $query = $this->createQuery();
        $query->matching(
            $query->equals('name', $name)
        );
        $this->debugQuery($query, 'Debug SQL in repository with QueryInterface');
        return $query->execute();
    }

    public function queryByName($name)
    {
        /** @var ConnectionPool $pool */
        $pool = GeneralUtility::makeInstance(ConnectionPool::class);
        $connection = $pool->getConnectionForTable('tx_extkey_domain_model_yourmodel');
        $queryBuilder = $connection->createQueryBuilder();
        $query = $queryBuilder
            ->select('*')
            ->from('tx_extkey_domain_model_yourmodel')
            ->where("name like :name")
            ->setParameter('name', "%{$name}%");

        $this->debugQuery($query, 'Debug SQL in my repository with QueryBuilder');
        return $query->execute()->fetchAll();
    }
person biesior    schedule 03.08.2020
comment
Единственным недостатком является то, что все классы репозитория вынуждены наследоваться от AbstractRepository. Вместо этого было бы лучше иметь внешний класс API. - person Franz Holzinger; 04.08.2020
comment
В renderDebug() два массива следует поменять местами. В противном случае ':dcValue12' будет частично заменен поиском и заменой ':dcValue1'. В обратном порядке этого можно избежать. - person Julian Hofmann; 31.01.2021

Простой способ без изменения какого-либо основного кода Typo3 и до сих пор не упомянутый ни на одном форуме - это использование метода php "serialize()":

$result = $query->execute();
echo (serialize($result));

В объекте результата вы найдете SQL-запрос (ищите «оператор;» ...)

person Benno    schedule 08.11.2014

В v6.2x или более поздних версиях вы можете отлаживать объект результата в extBase, например:

В репозитории:

вернуть $запрос->выполнить(истина); // "true" вернет результат массива

Или также вы можете отлаживать объект результата в контроллере:

$resultObject = $this->yourRepository->findAll();
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($resultObject);

person Ghanshyam Gohel    schedule 21.11.2015

Здесь я публикую метод, который вы можете ввести для отладки в любом классе, и сделать его трейтом тоже можно. Авторство и источник упоминаются в комментарии, использование тоже:


    /**
    * Render the generated SQL of a query in TYPO3 8
    *
    * @author wp_bube https://www.typo3.net/forum/user-profil/benutzer/zeige/benutzer/wp-bube/
    * @src   https://www.typo3.net/forum/thematik/zeige/thema/125747/
    *
    * Usage: $this->debugQuery($query);
    *
    * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
    * @param bool $format
    * @param bool $exit
    */
    private function debugQuery($query, $format = true, $exit = true)
    {
        function getFormattedSQL($sql_raw)
        {
            if (empty($sql_raw) || !is_string($sql_raw)) {
                return false;
            }
            $sql_reserved_all = array( 'ACCESSIBLE', 'ACTION', 'ADD', 'AFTER', 'AGAINST', 'AGGREGATE', 'ALGORITHM', 'ALL', 'ALTER', 'ANALYSE', 'ANALYZE', 'AND', 'AS', 'ASC', 'AUTOCOMMIT', 'AUTO_INCREMENT', 'AVG_ROW_LENGTH', 'BACKUP', 'BEGIN', 'BETWEEN', 'BINLOG', 'BOTH', 'BY', 'CASCADE', 'CASE', 'CHANGE', 'CHANGED', 'CHARSET', 'CHECK', 'CHECKSUM', 'COLLATE', 'COLLATION', 'COLUMN', 'COLUMNS', 'COMMENT', 'COMMIT', 'COMMITTED', 'COMPRESSED', 'CONCURRENT', 'CONSTRAINT', 'CONTAINS', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_TIMESTAMP', 'DATABASE', 'DATABASES', 'DAY', 'DAY_HOUR', 'DAY_MINUTE', 'DAY_SECOND', 'DEFINER', 'DELAYED', 'DELAY_KEY_WRITE', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DO', 'DROP', 'DUMPFILE', 'DUPLICATE', 'DYNAMIC', 'ELSE', 'ENCLOSED', 'END', 'ENGINE', 'ENGINES', 'ESCAPE', 'ESCAPED', 'EVENTS', 'EXECUTE', 'EXISTS', 'EXPLAIN', 'EXTENDED', 'FAST', 'FIELDS', 'FILE', 'FIRST', 'FIXED', 'FLUSH', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULL', 'FULLTEXT', 'FUNCTION', 'GEMINI', 'GEMINI_SPIN_RETRIES', 'GLOBAL', 'GRANT', 'GRANTS', 'GROUP', 'HAVING', 'HEAP', 'HIGH_PRIORITY', 'HOSTS', 'HOUR', 'HOUR_MINUTE', 'HOUR_SECOND', 'IDENTIFIED', 'IF', 'IGNORE', 'IN', 'INDEX', 'INDEXES', 'INFILE', 'INNER', 'INSERT', 'INSERT_ID', 'INSERT_METHOD', 'INTERVAL', 'INTO', 'INVOKER', 'IS', 'ISOLATION', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LAST_INSERT_ID', 'LEADING', 'LEFT', 'LEVEL', 'LIKE', 'LIMIT', 'LINEAR', 'LINES', 'LOAD', 'LOCAL', 'LOCK', 'LOCKS', 'LOGS', 'LOW_PRIORITY', 'MARIA', 'MASTER', 'MASTER_CONNECT_RETRY', 'MASTER_HOST', 'MASTER_LOG_FILE', 'MASTER_LOG_POS', 'MASTER_PASSWORD', 'MASTER_PORT', 'MASTER_USER', 'MATCH', 'MAX_CONNECTIONS_PER_HOUR', 'MAX_QUERIES_PER_HOUR', 'MAX_ROWS', 'MAX_UPDATES_PER_HOUR', 'MAX_USER_CONNECTIONS', 'MEDIUM', 'MERGE', 'MINUTE', 'MINUTE_SECOND', 'MIN_ROWS', 'MODE', 'MODIFY', 'MONTH', 'MRG_MYISAM', 'MYISAM', 'NAMES', 'NATURAL', 'NOT', 'NULL', 'OFFSET', 'ON', 'OPEN', 'OPTIMIZE', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUTER', 'OUTFILE', 'PACK_KEYS', 'PAGE', 'PARTIAL', 'PARTITION', 'PARTITIONS', 'PASSWORD', 'PRIMARY', 'PRIVILEGES', 'PROCEDURE', 'PROCESS', 'PROCESSLIST', 'PURGE', 'QUICK', 'RAID0', 'RAID_CHUNKS', 'RAID_CHUNKSIZE', 'RAID_TYPE', 'RANGE', 'READ', 'READ_ONLY', 'READ_WRITE', 'REFERENCES', 'REGEXP', 'RELOAD', 'RENAME', 'REPAIR', 'REPEATABLE', 'REPLACE', 'REPLICATION', 'RESET', 'RESTORE', 'RESTRICT', 'RETURN', 'RETURNS', 'REVOKE', 'RIGHT', 'RLIKE', 'ROLLBACK', 'ROW', 'ROWS', 'ROW_FORMAT', 'SECOND', 'SECURITY', 'SELECT', 'SEPARATOR', 'SERIALIZABLE', 'SESSION', 'SET', 'SHARE', 'SHOW', 'SHUTDOWN', 'SLAVE', 'SONAME', 'SOUNDS', 'SQL', 'SQL_AUTO_IS_NULL', 'SQL_BIG_RESULT', 'SQL_BIG_SELECTS', 'SQL_BIG_TABLES', 'SQL_BUFFER_RESULT', 'SQL_CACHE', 'SQL_CALC_FOUND_ROWS', 'SQL_LOG_BIN', 'SQL_LOG_OFF', 'SQL_LOG_UPDATE', 'SQL_LOW_PRIORITY_UPDATES', 'SQL_MAX_JOIN_SIZE', 'SQL_NO_CACHE', 'SQL_QUOTE_SHOW_CREATE', 'SQL_SAFE_UPDATES', 'SQL_SELECT_LIMIT', 'SQL_SLAVE_SKIP_COUNTER', 'SQL_SMALL_RESULT', 'SQL_WARNINGS', 'START', 'STARTING', 'STATUS', 'STOP', 'STORAGE', 'STRAIGHT_JOIN', 'STRING', 'STRIPED', 'SUPER', 'TABLE', 'TABLES', 'TEMPORARY', 'TERMINATED', 'THEN', 'TO', 'TRAILING', 'TRANSACTIONAL', 'TRUNCATE', 'TYPE', 'TYPES', 'UNCOMMITTED', 'UNION', 'UNIQUE', 'UNLOCK', 'UPDATE', 'USAGE', 'USE', 'USING', 'VALUES', 'VARIABLES', 'VIEW', 'WHEN', 'WHERE', 'WITH', 'WORK', 'WRITE', 'XOR', 'YEAR_MONTH' );

            $sql_skip_reserved_words = array('AS', 'ON', 'USING');
            $sql_special_reserved_words = array('(', ')');
            $sql_raw = str_replace("\n", " ", $sql_raw);
            $sql_formatted = "";
            $prev_word = "";
            $word = "";
            for ($i = 0, $j = strlen($sql_raw); $i < $j; $i++) {
                $word .= $sql_raw[$i];
                $word_trimmed = trim($word);
                if ($sql_raw[$i] == " " || in_array($sql_raw[$i], $sql_special_reserved_words)) {
                    $word_trimmed = trim($word);
                    $trimmed_special = false;
                    if (in_array($sql_raw[$i], $sql_special_reserved_words)) {
                        $word_trimmed = substr($word_trimmed, 0, -1);
                        $trimmed_special = true;
                    }
                    $word_trimmed = strtoupper($word_trimmed);
                    if (in_array($word_trimmed, $sql_reserved_all) && !in_array($word_trimmed, $sql_skip_reserved_words)) {
                        if (in_array($prev_word, $sql_reserved_all)) {
                            $sql_formatted .= '<b>' . strtoupper(trim($word)) . '</b>' . '&nbsp;';
                        } else {
                            $sql_formatted .= '<br/>&nbsp;';
                            $sql_formatted .= '<b>' . strtoupper(trim($word)) . '</b>' . '&nbsp;';
                        }
                        $prev_word = $word_trimmed;
                        $word = "";
                    } else {
                        $sql_formatted .= trim($word) . '&nbsp;';
                        $prev_word = $word_trimmed;
                        $word = "";
                    }
                }
            }
            $sql_formatted .= trim($word);
            return $sql_formatted;
        }

        $queryParser          = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class);
        $doctrineQueryBuilder = $queryParser->convertQueryToDoctrineQueryBuilder($query);
        $preparedStatement    = $doctrineQueryBuilder->getSQL();
        $parameters           = $doctrineQueryBuilder->getParameters();
        $stringParams = [];
        foreach ($parameters as $key => $parameter) {
            $stringParams[':' . $key] = $parameter;
        }
        $statement = strtr($preparedStatement, $stringParams);
        if ($format) {
            echo '<code>' . getFormattedSQL($statement) . '</code>';
        } else {
            echo $statement;
        }
        if ($exit) {
            exit;
        }
    }
person David    schedule 31.08.2019

person    schedule
comment
Это не работает для меня вообще. Что такое $GLOBALS['TYPO3_DB']-›debugOutput = true; должен сделать? Я не получаю результатов. Я использую TYPO3 4.6.6 здесь и не могу получить SQL, сгенерированный Extbase. Почему это так сложно? - person Martin; 15.02.2013
comment
@Martin: см. api.typo3.org/typo3cms/47/html/ - person Der Hochstapler; 19.03.2013
comment
Чтобы расширить: вы также можете назначить 1 (так же, как true, для display queries with errors) или 2 (для display all queries). - person pdu; 19.03.2013
comment
$GLOBALS['TYPO3_DB']->debugOutput = 2; НЕ работает... я не понимаю, почему так сложно просто включить вывод sql-запросов... - person Besnik; 21.06.2013