[TASK] Doctrine: Migrate remaining parts of ContentObjectRenderer 28/49728/8
authorMorton Jonuschat <m.jonuschat@mojocode.de>
Thu, 1 Sep 2016 14:04:37 +0000 (16:04 +0200)
committerWouter Wolters <typo3@wouterwolters.nl>
Thu, 1 Sep 2016 21:32:55 +0000 (23:32 +0200)
Change-Id: I89d7dbce11dd7b3d491752a2ba8a1cd889a759b1
Resolves: #77750
Releases: master
Reviewed-on: https://review.typo3.org/49728
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Bamboo TYPO3com <info@typo3.com>
Reviewed-by: Philipp Gampe <philipp.gampe@typo3.org>
Tested-by: Philipp Gampe <philipp.gampe@typo3.org>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
typo3/sysext/core/Classes/Database/Query/QueryBuilder.php
typo3/sysext/core/Classes/Database/Query/QueryHelper.php
typo3/sysext/core/Documentation/Changelog/master/Breaking-77750-ReturnValueOfexec_QueryOfContentObjectRendererChanged.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Database/Query/QueryHelperTest.php
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
typo3/sysext/frontend/Classes/ContentObject/Menu/AbstractMenuContentObject.php
typo3/sysext/frontend/Tests/Functional/ContentObject/ContentObjectRendererTest.php
typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php

index 94d2bca..5720b42 100644 (file)
@@ -301,7 +301,7 @@ class QueryBuilder
      */
     public function getFirstResult(): int
     {
-        return $this->concreteQueryBuilder->getFirstResult();
+        return (int)$this->concreteQueryBuilder->getFirstResult();
     }
 
     /**
index ff5189f..f50774e 100644 (file)
@@ -102,6 +102,52 @@ class QueryHelper
     }
 
     /**
+     * Split a JOIN SQL fragment into table name, alias and join conditions.
+     *
+     * @param string $input eg. "JOIN tableName AS a ON a.uid = anotherTable.uid_foreign"
+     * @return array assoc array consisting of the keys tableName, tableAlias and joinCondition
+     */
+    public static function parseJoin(string $input): array
+    {
+        $input = trim($input);
+        $quoteCharacter = ' ';
+        // Check if the tableName is quoted
+        if ($input[0] === '`' || $input[0] === '"') {
+            $quoteCharacter .= $input[0];
+            $input = substr($input, 1);
+            $tableName = strtok($input, $quoteCharacter);
+        } else {
+            $tableName = strtok($input, $quoteCharacter);
+        }
+
+        $tableAlias = strtok($quoteCharacter);
+        if (strtolower($tableAlias) === 'as') {
+            $tableAlias = strtok($quoteCharacter);
+            // Skip the next token which must be ON
+            strtok(' ');
+            $joinCondition = strtok('');
+        } elseif (strtolower($tableAlias) === 'on') {
+            $tableAlias = null;
+            $joinCondition = strtok('');
+        } else {
+            // Skip the next token which must be ON
+            strtok(' ');
+            $joinCondition = strtok('');
+        }
+
+        // Catch the edge case that the table name is unquoted and the
+        // table alias is actually quoted. This will not work in the case
+        // that the quoted table alias contains whitespace.
+        if ($tableAlias[0] === '`' || $tableAlias[0] === '"') {
+            $tableAlias = substr($tableAlias, 1, -1);
+        }
+
+        $tableAlias = $tableAlias ?: $tableName;
+
+        return ['tableName' => $tableName, 'tableAlias' => $tableAlias, 'joinCondition' => $joinCondition];
+    }
+
+    /**
      * Removes the prefixes AND/OR from the input string.
      *
      * This function should be used when you can't guarantee that the string
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-77750-ReturnValueOfexec_QueryOfContentObjectRendererChanged.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-77750-ReturnValueOfexec_QueryOfContentObjectRendererChanged.rst
new file mode 100644 (file)
index 0000000..fecaf53
--- /dev/null
@@ -0,0 +1,35 @@
+============================================================================
+Breaking: #77750 - Return value of ContentObjectRenderer::exec_Query changed
+============================================================================
+
+Description
+===========
+
+The return type of :php:``ContentObjectRenderer::exec_Query()`` has changed.
+Instead of returning either :php:``bool``, :php:``\mysqli_result``
+or :php:``object`` the return value always is a :php:``\Doctrine\DBAL\Driver\Statement``.
+
+
+Impact
+======
+
+Using the mentioned method will not yield the expected result type.
+
+
+Affected Installations
+======================
+
+Any installation with a 3rd party extension that uses the named method.
+
+
+Migration
+=========
+
+Change the way the result is being used to conform to the Doctrine API:
+
+.. code-block:: php
+
+    $result = $this->cObj->exec_Query(...);
+    while ($row = $result->fetch()) {
+        // Do something here
+    }
index c66fe0b..e5a944e 100644 (file)
@@ -270,4 +270,82 @@ class QueryHelperTest extends UnitTestCase
     {
         $this->assertSame($expectedResult, QueryHelper::parseGroupBy($input));
     }
+
+    /**
+     * Test cases for parsing JOIN fragments into table name, alias and conditions
+     *
+     * @return array
+     */
+    public function parseJoinDataProvider(): array
+    {
+        return [
+            'unquoted tableName' => [
+                'aTable ON aTable.uid = anotherTable.uid_foreign',
+                [
+                    'tableName' => 'aTable',
+                    'tableAlias' => 'aTable',
+                    'joinCondition' => 'aTable.uid = anotherTable.uid_foreign'
+                ],
+            ],
+            'quoted tableName' => [
+                '`aTable` ON aTable.uid = anotherTable.uid_foreign',
+                [
+                    'tableName' => 'aTable',
+                    'tableAlias' => 'aTable',
+                    'joinCondition' => 'aTable.uid = anotherTable.uid_foreign'
+                ],
+            ],
+            'quoted tableName with alias' => [
+                '`aTable` a ON a.uid = anotherTable.uid_foreign',
+                [
+                    'tableName' => 'aTable',
+                    'tableAlias' => 'a',
+                    'joinCondition' => 'a.uid = anotherTable.uid_foreign'
+                ],
+            ],
+            'quoted tableName with quoted alias' => [
+                '`aTable` `a` ON a.uid = anotherTable.uid_foreign',
+                [
+                    'tableName' => 'aTable',
+                    'tableAlias' => 'a',
+                    'joinCondition' => 'a.uid = anotherTable.uid_foreign'
+                ],
+            ],
+            'quoted tableName with AS alias' => [
+                '`aTable` AS anAlias ON anAlias.uid = anotherTable.uid_foreign',
+                [
+                    'tableName' => 'aTable',
+                    'tableAlias' => 'anAlias',
+                    'joinCondition' => 'anAlias.uid = anotherTable.uid_foreign'
+                ],
+            ],
+            'quoted tableName with AS quoted alias' => [
+                '`aTable` AS `anAlias` ON anAlias.uid = anotherTable.uid_foreign',
+                [
+                    'tableName' => 'aTable',
+                    'tableAlias' => 'anAlias',
+                    'joinCondition' => 'anAlias.uid = anotherTable.uid_foreign'
+                ],
+            ],
+            'unquoted tableName with AS quoted alias' => [
+                'aTable AS `anAlias` ON anAlias.uid = anotherTable.uid_foreign',
+                [
+                    'tableName' => 'aTable',
+                    'tableAlias' => 'anAlias',
+                    'joinCondition' => 'anAlias.uid = anotherTable.uid_foreign'
+                ],
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider parseJoinDataProvider
+     * @param string $input
+     * @param array $expected
+     */
+    public function parseJoinSplitsStatement(string $input, array $expected)
+    {
+        $this->assertSame($expected, QueryHelper::parseJoin($input));
+    }
 }
index c760ece..5873777 100644 (file)
@@ -15,9 +15,11 @@ namespace TYPO3\CMS\Frontend\ContentObject;
  */
 
 use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Driver\Statement;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Charset\CharsetConverter;
 use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
@@ -3509,24 +3511,16 @@ class ContentObjectRenderer
      * Implements the stdWrap "numRows" property
      *
      * @param array $conf TypoScript properties for the property (see link to "numRows")
-     * @return int|bool The number of rows found by the select (FALSE on error)
+     * @return int The number of rows found by the select
      * @access private
      * @see stdWrap()
      */
     public function numRows($conf)
     {
-        $result = false;
         $conf['select.']['selectFields'] = 'count(*)';
-        $res = $this->exec_getQuery($conf['table'], $conf['select.']);
-        $db = $this->getDatabaseConnection();
-        if ($error = $db->sql_error()) {
-            $this->getTimeTracker()->setTSlogMessage($error, 3);
-        } else {
-            $row = $db->sql_fetch_row($res);
-            $result = (int)$row[0];
-        }
-        $db->sql_free_result($res);
-        return $result;
+        $statement = $this->exec_getQuery($conf['table'], $conf['select.']);
+
+        return (int)$statement->fetchColumn(0);
     }
 
     /**
@@ -7381,13 +7375,15 @@ class ContentObjectRenderer
      *
      * @param string $table The table name
      * @param array $conf The TypoScript configuration properties
-     * @return bool|\mysqli_result|object MySQLi result object / DBAL object
+     * @return Statement
      * @see getQuery()
      */
     public function exec_getQuery($table, $conf)
     {
-        $queryParts = $this->getQuery($table, $conf, true);
-        return $this->getDatabaseConnection()->exec_SELECT_queryArray($queryParts);
+        $statement = $this->getQuery($table, $conf);
+        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
+
+        return $connection->executeQuery($statement);
     }
 
     /**
@@ -7397,43 +7393,37 @@ class ContentObjectRenderer
      * @param string $tableName the name of the TCA database table
      * @param array $queryConfiguration The TypoScript configuration properties, see .select in TypoScript reference
      * @return array The records
+     * @throws \UnexpectedValueException
      */
     public function getRecords($tableName, array $queryConfiguration)
     {
         $records = [];
 
-        $res = $this->exec_getQuery($tableName, $queryConfiguration);
-
-        $db = $this->getDatabaseConnection();
-        if ($error = $db->sql_error()) {
-            $this->getTimeTracker()->setTSlogMessage($error, 3);
-        } else {
-            $tsfe = $this->getTypoScriptFrontendController();
-            while (($row = $db->sql_fetch_assoc($res)) !== false) {
-
-                // Versioning preview:
-                $tsfe->sys_page->versionOL($tableName, $row, true);
+        $statement = $this->exec_getQuery($tableName, $queryConfiguration);
 
-                // Language overlay:
-                if (is_array($row) && $tsfe->sys_language_contentOL) {
-                    if ($tableName === 'pages') {
-                        $row = $tsfe->sys_page->getPageOverlay($row);
-                    } else {
-                        $row = $tsfe->sys_page->getRecordOverlay(
-                            $tableName,
-                            $row,
-                            $tsfe->sys_language_content,
-                            $tsfe->sys_language_contentOL
-                        );
-                    }
+        $tsfe = $this->getTypoScriptFrontendController();
+        while ($row = $statement->fetch()) {
+            // Versioning preview:
+            $tsfe->sys_page->versionOL($tableName, $row, true);
+
+            // Language overlay:
+            if (is_array($row) && $tsfe->sys_language_contentOL) {
+                if ($tableName === 'pages') {
+                    $row = $tsfe->sys_page->getPageOverlay($row);
+                } else {
+                    $row = $tsfe->sys_page->getRecordOverlay(
+                        $tableName,
+                        $row,
+                        $tsfe->sys_language_content,
+                        $tsfe->sys_language_contentOL
+                    );
                 }
+            }
 
-                // Might be unset in the sys_language_contentOL
-                if (is_array($row)) {
-                    $records[] = $row;
-                }
+            // Might be unset in the sys_language_contentOL
+            if (is_array($row)) {
+                $records[] = $row;
             }
-            $db->sql_free_result($res);
         }
 
         return $records;
@@ -7447,6 +7437,8 @@ class ContentObjectRenderer
      * @param array $conf See ->exec_getQuery()
      * @param bool $returnQueryArray If set, the function will return the query not as a string but array with the various parts. RECOMMENDED!
      * @return mixed A SELECT query if $returnQueryArray is FALSE, otherwise the SELECT query in an array as parts.
+     * @throws \RuntimeException
+     * @throws \InvalidArgumentException
      * @access private
      * @see CONTENT(), numRows()
      */
@@ -7503,6 +7495,7 @@ class ContentObjectRenderer
                 }
             }
         }
+
         // Construct WHERE clause:
         // Handle recursive function for the pidInList
         if (isset($conf['recursive'])) {
@@ -7532,117 +7525,247 @@ class ContentObjectRenderer
         if ((string)$conf['pidInList'] === '') {
             $conf['pidInList'] = 'this';
         }
-        $queryParts = $this->getWhere($table, $conf, true);
+
+        $queryParts = $this->getQueryConstraints($table, $conf);
+
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+        // @todo Check against getQueryConstraints, can probably use FrontendRestrictions
+        // @todo here and remove enableFields there.
+        $queryBuilder->getRestrictions()->removeAll();
+        $queryBuilder->select('*')->from($table);
+
+        if ($queryParts['where']) {
+            $queryBuilder->where($queryParts['where']);
+        }
+
+        if ($queryParts['groupBy']) {
+            $queryBuilder->groupBy(...$queryParts['groupBy']);
+        }
+
         // Fields:
         if ($conf['selectFields']) {
-            $queryParts['SELECT'] = $this->sanitizeSelectPart($conf['selectFields'], $table);
-        } else {
-            $queryParts['SELECT'] = '*';
+            $queryBuilder->selectLiteral($this->sanitizeSelectPart($conf['selectFields'], $table));
         }
+
         // Setting LIMIT:
-        $db = $this->getDatabaseConnection();
-        $error = 0;
+        $error = false;
         if ($conf['max'] || $conf['begin']) {
             // Finding the total number of records, if used:
-            if (strstr(strtolower($conf['begin'] . $conf['max']), 'total')) {
-                $res = $db->exec_SELECTquery('count(*)', $table, $queryParts['WHERE'], $queryParts['GROUPBY']);
-                if ($error = $db->sql_error()) {
-                    $this->getTimeTracker()->setTSlogMessage($error);
-                } else {
-                    $row = $db->sql_fetch_row($res);
-                    $conf['max'] = str_ireplace('total', $row[0], $conf['max']);
-                    $conf['begin'] = str_ireplace('total', $row[0], $conf['begin']);
+            if (strpos(strtolower($conf['begin'] . $conf['max']), 'total') !== false) {
+                $countQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+                $countQueryBuilder->getRestrictions()->removeAll();
+                $countQueryBuilder->count('*')
+                    ->from($table)
+                    ->where($queryParts['where']);
+
+                if ($queryParts['groupBy']) {
+                    $countQueryBuilder->groupBy(...$queryParts['groupBy']);
+                }
+
+                try {
+                    $count = $countQueryBuilder->execute()->fetchColumn(0);
+                    $conf['max'] = str_ireplace('total', $count, $conf['max']);
+                    $conf['begin'] = str_ireplace('total', $count, $conf['begin']);
+                } catch (DBALException $e) {
+                    $this->getTimeTracker()->setTSlogMessage($e->getPrevious()->getMessage());
+                    $error = true;
                 }
-                $db->sql_free_result($res);
             }
+
             if (!$error) {
                 $conf['begin'] = MathUtility::forceIntegerInRange(ceil($this->calc($conf['begin'])), 0);
                 $conf['max'] = MathUtility::forceIntegerInRange(ceil($this->calc($conf['max'])), 0);
-                if ($conf['begin'] && !$conf['max']) {
-                    $conf['max'] = 100000;
-                }
-                if ($conf['begin'] && $conf['max']) {
-                    $queryParts['LIMIT'] = $conf['begin'] . ',' . $conf['max'];
-                } elseif (!$conf['begin'] && $conf['max']) {
-                    $queryParts['LIMIT'] = $conf['max'];
+                if ($conf['begin'] > 0) {
+                    $queryBuilder->setFirstResult($conf['begin']);
                 }
+                $queryBuilder->setMaxResults($conf['max'] ?: 100000);
             }
         }
+
         if (!$error) {
             // Setting up tablejoins:
-            $joinPart = '';
             if ($conf['join']) {
-                $joinPart = 'JOIN ' . $conf['join'];
+                $joinParts = QueryHelper::parseJoin($conf['join']);
+                $queryBuilder->join(
+                    $table,
+                    $joinParts['tableName'],
+                    $joinParts['tableAlias'],
+                    $joinParts['joinCondition']
+                );
             } elseif ($conf['leftjoin']) {
-                $joinPart = 'LEFT OUTER JOIN ' . $conf['leftjoin'];
+                $joinParts = QueryHelper::parseJoin($conf['leftjoin']);
+                $queryBuilder->leftJoin(
+                    $table,
+                    $joinParts['tableName'],
+                    $joinParts['tableAlias'],
+                    $joinParts['joinCondition']
+                );
             } elseif ($conf['rightjoin']) {
-                $joinPart = 'RIGHT OUTER JOIN ' . $conf['rightjoin'];
+                $joinParts = QueryHelper::parseJoin($conf['rightjoin']);
+                $queryBuilder->rightJoin(
+                    $table,
+                    $joinParts['tableName'],
+                    $joinParts['tableAlias'],
+                    $joinParts['joinCondition']
+                );
             }
-            // Compile and return query:
-            $queryParts['FROM'] = trim($table . ' ' . $joinPart);
-            // Replace the markers in the queryParts to handle stdWrap
-            // enabled properties
+
+            // Convert the QueryBuilder object into a SQL statement.
+            $query = $queryBuilder->getSQL();
+
+            // Replace the markers in the queryParts to handle stdWrap enabled properties
             foreach ($queryMarkers as $marker => $markerValue) {
+                // @todo Ugly hack that needs to be cleaned up, with the current architecture
+                // @todo for exec_Query / getQuery it's the best we can do.
+                $query = str_replace('###' . $marker . '###', $markerValue, $query);
                 foreach ($queryParts as $queryPartKey => &$queryPartValue) {
                     $queryPartValue = str_replace('###' . $marker . '###', $markerValue, $queryPartValue);
                 }
                 unset($queryPartValue);
             }
-            $query = $db->SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT']);
-            return $returnQueryArray ? $queryParts : $query;
+
+            return $returnQueryArray ? $this->getQueryArray($queryBuilder) : $query;
         }
+
         return '';
     }
 
     /**
+     * Helper to transform a QueryBuilder object into a queryParts array that can be used
+     * with exec_SELECT_queryArray
+     *
+     * @param \TYPO3\CMS\Core\Database\Query\QueryBuilder $queryBuilder
+     * @return array
+     * @throws \RuntimeException
+     */
+    protected function getQueryArray(QueryBuilder $queryBuilder)
+    {
+        $fromClauses = [];
+        $knownAliases = [];
+        $queryParts = [];
+
+        // Loop through all FROM clauses
+        foreach ($queryBuilder->getQueryPart('from') as $from) {
+            if ($from['alias'] === null) {
+                $tableSql = $from['table'];
+                $tableReference = $from['table'];
+            } else {
+                $tableSql = $from['table'] . ' ' . $from['alias'];
+                $tableReference = $from['alias'];
+            }
+
+            $knownAliases[$tableReference] = true;
+
+            $fromClauses[$tableReference] = $tableSql . $this->getQueryArrayJoinHelper(
+                $tableReference,
+                $queryBuilder->getQueryPart('join'),
+                $knownAliases
+            );
+        }
+
+        $queryParts['SELECT'] = implode(', ', $queryBuilder->getQueryPart('select'));
+        $queryParts['FROM'] = implode(', ', $fromClauses);
+        $queryParts['WHERE'] = (string)$queryBuilder->getQueryPart('where') ?: '';
+        $queryParts['GROUPBY'] = implode(', ', $queryBuilder->getQueryPart('groupBy'));
+        $queryParts['ORDERBY'] = implode(', ', $queryBuilder->getQueryPart('orderBy'));
+        if ($queryBuilder->getFirstResult() > 0) {
+            $queryParts['LIMIT'] = $queryBuilder->getFirstResult() . ',' . $queryBuilder->getMaxResults();
+        } elseif ($queryBuilder->getMaxResults() > 0) {
+            $queryParts['LIMIT'] = $queryBuilder->getMaxResults();
+        }
+
+        return $queryParts;
+    }
+
+    /**
+     * Helper to transform the QueryBuilder join part into a SQL fragment.
+     *
+     * @param string $fromAlias
+     * @param array $joinParts
+     * @param array $knownAliases
+     * @return string
+     * @throws \RuntimeException
+     */
+    protected function getQueryArrayJoinHelper(string $fromAlias, array $joinParts, array &$knownAliases): string
+    {
+        $sql = '';
+
+        if (isset($joinParts['join'][$fromAlias])) {
+            foreach ($joinParts['join'][$fromAlias] as $join) {
+                if (array_key_exists($join['joinAlias'], $knownAliases)) {
+                    throw new \RuntimeException(
+                        'Non unique join alias: "' . $join['joinAlias'] . '" found.',
+                        1472748872
+                    );
+                }
+                $sql .= ' ' . strtoupper($join['joinType'])
+                    . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']
+                    . ' ON ' . ((string)$join['joinCondition']);
+                $knownAliases[$join['joinAlias']] = true;
+            }
+
+            foreach ($joinParts['join'][$fromAlias] as $join) {
+                $sql .= $this->getQueryArrayJoinHelper($join['joinAlias'], $joinParts, $knownAliases);
+            }
+        }
+
+        return $sql;
+    }
+    /**
      * Helper function for getQuery(), creating the WHERE clause of the SELECT query
      *
      * @param string $table The table name
      * @param array $conf The TypoScript configuration properties
-     * @param bool $returnQueryArray If set, the function will return the query not as a string but array with the various parts. RECOMMENDED!
-     * @return mixed A WHERE clause based on the relevant parts of the TypoScript properties for a "select" function in TypoScript, see link. If $returnQueryArray is FALSE the where clause is returned as a string with WHERE, GROUP BY and ORDER BY parts, otherwise as an array with these parts.
-     * @access private
+     * @return array Associative array containing the prepared data for WHERE, ORDER BY and GROUP BY fragments
+     * @throws \InvalidArgumentException
      * @see getQuery()
      */
-    public function getWhere($table, $conf, $returnQueryArray = false)
+    protected function getQueryConstraints(string $table, array $conf): array
     {
         // Init:
-        $query = '';
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+        $expressionBuilder = $queryBuilder->expr();
+        $tsfe = $this->getTypoScriptFrontendController();
+        $constraints = [];
         $pid_uid_flag = 0;
         $enableFieldsIgnore = [];
         $queryParts = [
-            'SELECT' => '',
-            'FROM' => '',
-            'WHERE' => '',
-            'GROUPBY' => '',
-            'ORDERBY' => '',
-            'LIMIT' => ''
+            'where' => null,
+            'groupBy' => null,
+            'orderBy' => null,
         ];
-        $tsfe = $this->getTypoScriptFrontendController();
+
         $considerMovePlaceholders = (
             $tsfe->sys_page->versioningPreview && $table !== 'pages'
             && !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS'])
         );
+
         if (trim($conf['uidInList'])) {
             $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $conf['uidInList']));
-            if (count($listArr) === 1) {
-                $comparison = '=' . (int)$listArr[0];
-            } else {
-                $comparison = ' IN (' . implode(',', $listArr) . ')';
-            }
+
             // If move placeholder shall be considered, select via t3ver_move_id
             if ($considerMovePlaceholders) {
-                $movePlaceholderComparison = $table . '.t3ver_state=' . VersionState::cast(VersionState::MOVE_PLACEHOLDER) . ' AND ' . $table . '.t3ver_move_id' . $comparison;
-                $query .= ' AND (' . $table . '.uid' . $comparison . ' OR ' . $movePlaceholderComparison . ')';
+                $constraints[] = (string)$expressionBuilder->orX(
+                    $expressionBuilder->in($table . '.uid', $listArr),
+                    $expressionBuilder->andX(
+                        $expressionBuilder->eq(
+                            $table . '.t3ver_state',
+                            VersionState::cast(VersionState::MOVE_PLACEHOLDER)
+                        ),
+                        $expressionBuilder->in($table . '.t3ver_move_id', $listArr)
+                    )
+                );
             } else {
-                $query .= ' AND ' . $table . '.uid' . $comparison;
+                $constraints[] = (string)$expressionBuilder->in($table . '.uid', $listArr);
             }
             $pid_uid_flag++;
         }
+
         // Static_* tables are allowed to be fetched from root page
-        if (substr($table, 0, 7) === 'static_') {
+        if (StringUtility::beginsWith($table, 'static_')) {
             $pid_uid_flag++;
         }
+
         if (trim($conf['pidInList'])) {
             $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $conf['pidInList']));
             // Removes all pages which are not visible for the user!
@@ -7655,20 +7778,22 @@ class ContentObjectRenderer
                 $enableFieldsIgnore['pid'] = true;
             }
             if (!empty($listArr)) {
-                $query .= ' AND ' . $table . '.pid IN (' . implode(',', array_map('intval', $listArr)) . ')';
+                $constraints[] = $expressionBuilder->in($table . '.pid', array_map('intval', $listArr));
                 $pid_uid_flag++;
             } else {
                 // If not uid and not pid then uid is set to 0 - which results in nothing!!
                 $pid_uid_flag = 0;
             }
         }
+
         // If not uid and not pid then uid is set to 0 - which results in nothing!!
         if (!$pid_uid_flag) {
-            $query .= ' AND ' . $table . '.uid=0';
+            $constraints[] = $expressionBuilder->eq($table . '.uid', 0);
         }
+
         $where = isset($conf['where.']) ? trim($this->stdWrap($conf['where'], $conf['where.'])) : trim($conf['where']);
         if ($where) {
-            $query .= ' AND ' . $where;
+            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($where);
         }
 
         // Check if the table is translatable, and set the language field by default from the TCA information
@@ -7688,44 +7813,113 @@ class ContentObjectRenderer
             if ($tsfe->sys_language_contentOL && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
                 // Sys language content is set to zero/-1 - and it is expected that whatever routine processes the output will
                 // OVERLAY the records with localized versions!
-                $languageQuery = $languageField . ' IN (0,-1)';
+                $languageQuery = $expressionBuilder->in($languageField, [0, -1]);
                 // Use this option to include records that don't have a default translation
                 // (originalpointerfield is 0 and the language field contains the requested language)
                 $includeRecordsWithoutDefaultTranslation = isset($conf['includeRecordsWithoutDefaultTranslation.']) ?
                     $this->stdWrap($conf['includeRecordsWithoutDefaultTranslation'], $conf['includeRecordsWithoutDefaultTranslation.']) :
                     $conf['includeRecordsWithoutDefaultTranslation'];
-                if (!empty(trim($includeRecordsWithoutDefaultTranslation))) {
-                    $languageQuery .= ' OR (' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . ' = 0 AND ' .
-                        $languageField . ' = ' . $sys_language_content . ')';
+                if (trim($includeRecordsWithoutDefaultTranslation) !== '') {
+                    $languageQuery = $expressionBuilder->orX(
+                        $languageQuery,
+                        $expressionBuilder->andX(
+                            $expressionBuilder->eq($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], 0),
+                            $expressionBuilder->eq($languageField, $sys_language_content)
+                        )
+                    );
                 }
             } else {
-                $languageQuery = $languageField . ' = ' . $sys_language_content;
+                $languageQuery = $expressionBuilder->eq($languageField, $sys_language_content);
             }
-            $query .= ' AND (' . $languageQuery . ')';
+            $constraints[] = $languageQuery;
         }
 
         // Enablefields
         if ($table === 'pages') {
-            $query .= ' ' . $tsfe->sys_page->where_hid_del . $tsfe->sys_page->where_groupAccess;
+            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_hid_del);
+            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_groupAccess);
         } else {
-            $query .= $this->enableFields($table, false, $enableFieldsIgnore);
+            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($this->enableFields($table, false, $enableFieldsIgnore));
         }
+
         // MAKE WHERE:
-        if ($query) {
-            // Stripping of " AND"...
-            $queryParts['WHERE'] = trim(substr($query, 4));
-            $query = 'WHERE ' . $queryParts['WHERE'];
+        if (count($constraints) !== 0) {
+            $queryParts['where'] = $expressionBuilder->andX(...$constraints);
         }
         // GROUP BY
         if (trim($conf['groupBy'])) {
-            $queryParts['GROUPBY'] = isset($conf['groupBy.']) ? trim($this->stdWrap($conf['groupBy'], $conf['groupBy.'])) : trim($conf['groupBy']);
-            $query .= ' GROUP BY ' . $queryParts['GROUPBY'];
+            $groupBy = isset($conf['groupBy.'])
+                ? trim($this->stdWrap($conf['groupBy'], $conf['groupBy.']))
+                : trim($conf['groupBy']);
+            $queryParts['groupBy'] = QueryHelper::parseGroupBy($groupBy);
         }
+
         // ORDER BY
         if (trim($conf['orderBy'])) {
-            $queryParts['ORDERBY'] = isset($conf['orderBy.']) ? trim($this->stdWrap($conf['orderBy'], $conf['orderBy.'])) : trim($conf['orderBy']);
+            $orderByString = isset($conf['orderBy.'])
+                ? trim($this->stdWrap($conf['orderBy'], $conf['orderBy.']))
+                : trim($conf['orderBy']);
+
+            $queryParts['orderBy'] = QueryHelper::parseOrderBy($orderByString);
+        }
+
+        // Return result:
+        return $queryParts;
+    }
+
+    /**
+     * Helper function for getQuery(), creating the WHERE clause of the SELECT query
+     *
+     * @param string $table The table name
+     * @param array $conf The TypoScript configuration properties
+     * @param bool $returnQueryArray If set, the function will return the query not as a string but array with the various parts. RECOMMENDED!
+     * @return mixed A WHERE clause based on the relevant parts of the TypoScript properties for a "select" function in TypoScript, see link. If $returnQueryArray is FALSE the where clause is returned as a string with WHERE, GROUP BY and ORDER BY parts, otherwise as an array with these parts.
+     * @access private
+     * @see getQuery()
+     * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
+     */
+    public function getWhere($table, $conf, $returnQueryArray = false)
+    {
+        // Init:
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+        $queryConstraints = $this->getQueryConstraints($table, $conf);
+        $query = '';
+
+        $queryParts = [
+            'SELECT' => '',
+            'FROM' => '',
+            'WHERE' => '',
+            'GROUPBY' => '',
+            'ORDERBY' => '',
+            'LIMIT' => ''
+        ];
+
+        // MAKE WHERE:
+        if (!empty($queryConstraints['where'])) {
+            $queryParts['WHERE'] = (string)$queryConstraints['where'];
+            $query = 'WHERE ' . $queryParts['WHERE'];
+        }
+
+        // GROUP BY
+        if (!empty($queryConstraints['groupBy'])) {
+            $queryParts['GROUPBY'] = implode(
+                ', ',
+                array_map([$queryBuilder, 'quoteIdentifier'], $queryConstraints['groupBy'])
+            );
+            $query .= ' GROUP BY ' . $queryParts['GROUPBY'];
+        }
+
+        // ORDER BY
+        if (!empty($queryConstraints['orderBy'])) {
+            $orderBy = [];
+            foreach ($queryConstraints['orderBy'] as $orderPair) {
+                list($fieldName, $direction) = $orderPair;
+                $orderBy[] = trim($queryBuilder->quoteIdentifier($fieldName) . ' ' . $direction);
+            }
+            $queryParts['ORDERBY'] = implode(', ', $orderBy);
             $query .= ' ORDER BY ' . $queryParts['ORDERBY'];
         }
+
         // Return result:
         return $returnQueryArray ? $queryParts : $query;
     }
@@ -7744,6 +7938,8 @@ class ContentObjectRenderer
      */
     protected function sanitizeSelectPart($selectPart, $table)
     {
+        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
+
         // Pattern matching parts
         $matchStart = '/(^\\s*|,\\s*|' . $table . '\\.)';
         $matchEnd = '(\\s*,|\\s*$)/';
@@ -7753,14 +7949,14 @@ class ContentObjectRenderer
             foreach ($necessaryFields as $field) {
                 $match = $matchStart . $field . $matchEnd;
                 if (!preg_match($match, $selectPart)) {
-                    $selectPart .= ', ' . $table . '.' . $field . ' as ' . $field;
+                    $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
                 }
             }
             if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
                 foreach ($wsFields as $field) {
                     $match = $matchStart . $field . $matchEnd;
                     if (!preg_match($match, $selectPart)) {
-                        $selectPart .= ', ' . $table . '.' . $field . ' as ' . $field;
+                        $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
                     }
                 }
             }
@@ -8096,16 +8292,6 @@ class ContentObjectRenderer
     }
 
     /**
-     * Returns the database connection
-     *
-     * @return \TYPO3\CMS\Core\Database\DatabaseConnection
-     */
-    protected function getDatabaseConnection()
-    {
-        return $GLOBALS['TYPO3_DB'];
-    }
-
-    /**
      * @return TimeTracker
      */
     protected function getTimeTracker()
index 9afca6c..67fbacc 100644 (file)
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Frontend\ContentObject\Menu;
 
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\DatabaseConnection;
 use TYPO3\CMS\Core\Database\RelationHandler;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\TypoScript\TemplateService;
@@ -699,7 +698,6 @@ abstract class AbstractMenuContentObject
     protected function prepareMenuItemsForDirectoryMenu($specialValue, $sortingField)
     {
         $tsfe = $this->getTypoScriptFrontendController();
-        $databaseConnection = $this->getDatabaseConnection();
         $menuItems = [];
         if ($specialValue == '') {
             $specialValue = $tsfe->page['uid'];
@@ -720,8 +718,8 @@ abstract class AbstractMenuContentObject
                 $id = $mount_info['mount_pid'];
             }
             // Get sub-pages:
-            $res = $this->parent_cObj->exec_getQuery('pages', ['pidInList' => $id, 'orderBy' => $sortingField]);
-            while ($row = $databaseConnection->sql_fetch_assoc($res)) {
+            $statement = $this->parent_cObj->exec_getQuery('pages', ['pidInList' => $id, 'orderBy' => $sortingField]);
+            while ($row = $statement->fetch()) {
                 $tsfe->sys_page->versionOL('pages', $row, true);
                 if (!empty($row)) {
                     // Keep mount point?
@@ -749,8 +747,8 @@ abstract class AbstractMenuContentObject
                     }
                 }
             }
-            $databaseConnection->sql_free_result($res);
         }
+
         return $menuItems;
     }
 
@@ -878,20 +876,20 @@ abstract class AbstractMenuContentObject
         if ($maxAge > 0) {
             $extraWhere .= ' AND ' . $sortField . '>' . ($GLOBALS['SIM_ACCESS_TIME'] - $maxAge);
         }
-        $res = $this->parent_cObj->exec_getQuery('pages', [
+        $statement = $this->parent_cObj->exec_getQuery('pages', [
             'pidInList' => '0',
             'uidInList' => $id_list,
             'where' => $sortField . '>=0' . $extraWhere,
             'orderBy' => $sortingField ?: $sortField . ' DESC',
             'max' => $limit
         ]);
-        while ($row = $this->getDatabaseConnection()->sql_fetch_assoc($res)) {
+        while ($row = $statement->fetch()) {
             $tsfe->sys_page->versionOL('pages', $row, true);
             if (is_array($row)) {
                 $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
             }
         }
-        $this->getDatabaseConnection()->sql_free_result($res);
+
         return $menuItems;
     }
 
@@ -975,21 +973,21 @@ abstract class AbstractMenuContentObject
                 );
             }
             $where = empty($keyWordsWhereArr) ? '' : '(' . implode(' OR ', $keyWordsWhereArr) . ')';
-            $res = $this->parent_cObj->exec_getQuery('pages', [
+            $statement = $this->parent_cObj->exec_getQuery('pages', [
                 'pidInList' => '0',
                 'uidInList' => $id_list,
                 'where' => $where . $extraWhere,
                 'orderBy' => $sortingField ?: $sortField . ' desc',
                 'max' => $limit
             ]);
-            while (($row = $this->getDatabaseConnection()->sql_fetch_assoc($res))) {
+            while ($row = $statement->fetch()) {
                 $tsfe->sys_page->versionOL('pages', $row, true);
                 if (is_array($row)) {
                     $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
                 }
             }
-            $this->getDatabaseConnection()->sql_free_result($res);
         }
+
         return $menuItems;
     }
 
@@ -2134,13 +2132,13 @@ abstract class AbstractMenuContentObject
             // the referenced page
             $selectSetup['pidInList'] = $basePageRow['content_from_pid'];
         }
-        $resource = $this->parent_cObj->exec_getQuery('tt_content', $selectSetup);
-        if (!$resource) {
+        $statement = $this->parent_cObj->exec_getQuery('tt_content', $selectSetup);
+        if (!$statement) {
             $message = 'SectionIndex: Query to fetch the content elements failed!';
             throw new \UnexpectedValueException($message, 1337334849);
         }
         $result = [];
-        while ($row = $this->getDatabaseConnection()->sql_fetch_assoc($resource)) {
+        while ($row = $statement->fetch()) {
             $this->sys_page->versionOL('tt_content', $row);
             if ($tsfe->sys_language_contentOL && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
                 $row = $this->sys_page->getRecordOverlay('tt_content', $row, $basePageRow['_PAGES_OVERLAY_LANGUAGE'], $tsfe->sys_language_contentOL);
@@ -2171,7 +2169,7 @@ abstract class AbstractMenuContentObject
                 $result[$uid]['sectionIndex_uid'] = $uid;
             }
         }
-        $this->getDatabaseConnection()->sql_free_result($resource);
+
         return $result;
     }
 
@@ -2196,14 +2194,6 @@ abstract class AbstractMenuContentObject
     }
 
     /**
-     * @return DatabaseConnection
-     */
-    protected function getDatabaseConnection()
-    {
-        return $GLOBALS['TYPO3_DB'];
-    }
-
-    /**
      * @return TypoScriptFrontendController
      */
     protected function getTypoScriptFrontendController()
index 657a615..8f75208 100644 (file)
@@ -69,7 +69,7 @@ class ContentObjectRendererTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase
                     'selectFields' => 'header,bodytext'
                 ],
                 [
-                    'SELECT' => 'header,bodytext, tt_content.uid as uid, tt_content.pid as pid, tt_content.t3ver_state as t3ver_state'
+                    'SELECT' => 'header,bodytext, `tt_content`.`uid` AS `uid`, `tt_content`.`pid` AS `pid`, `tt_content`.`t3ver_state` AS `t3ver_state`'
                 ]
             ],
             'testing #17284: no need to add' => [
@@ -97,7 +97,7 @@ class ContentObjectRendererTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase
                     'join' => 'be_users ON tt_content.cruser_id = be_users.uid'
                 ],
                 [
-                    'SELECT' => 'tt_content.header,be_users.username, tt_content.uid as uid, tt_content.pid as pid, tt_content.t3ver_state as t3ver_state'
+                    'SELECT' => 'tt_content.header,be_users.username, `tt_content`.`uid` AS `uid`, `tt_content`.`pid` AS `pid`, `tt_content`.`t3ver_state` AS `t3ver_state`'
                 ]
             ],
             'testing #34152: single count(*), add nothing' => [
@@ -256,7 +256,7 @@ class ContentObjectRendererTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase
                     'groupBy' => 'tt_content.title',
                     'orderBy' => 'tt_content.sorting',
                 ],
-                'WHERE tt_content.uid=42 AND tt_content.pid IN (43) AND tt_content.cruser_id=5 GROUP BY tt_content.title ORDER BY tt_content.sorting',
+                'WHERE (`tt_content`.`uid` IN (42)) AND (`tt_content`.`pid` IN (43)) AND (tt_content.cruser_id=5) GROUP BY `tt_content`.`title` ORDER BY `tt_content`.`sorting`',
             ],
             [
                 [
@@ -283,7 +283,7 @@ class ContentObjectRendererTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase
                     'groupBy' => 'tt_content.title',
                     'orderBy' => 'tt_content.sorting',
                 ],
-                'WHERE tt_content.uid=42 AND tt_content.pid IN (43) AND tt_content.cruser_id=5 AND (tt_content.sys_language_uid = 13)%s GROUP BY tt_content.title ORDER BY tt_content.sorting',
+                'WHERE (`tt_content`.`uid` IN (42)) AND (`tt_content`.`pid` IN (43)) AND (tt_content.cruser_id=5) AND (`tt_content`.`sys_language_uid` = 13) AND ((`tt_content`.`deleted` = 0) AND (`tt_content`.`hidden` = 0) AND (`tt_content`.`startdate` <= 4242) AND ((`tt_content`.`enddate` = 0) OR (`tt_content`.`enddate` > 4242))) GROUP BY `tt_content`.`title` ORDER BY `tt_content`.`sorting`',
             ],
             [
                 [
@@ -303,7 +303,7 @@ class ContentObjectRendererTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase
                     'where' => 'tt_content.cruser_id=5',
                     'languageField' => 0,
                 ],
-                'WHERE tt_content.uid=42 AND tt_content.pid IN (43) AND tt_content.cruser_id=5',
+                'WHERE (`tt_content`.`uid` IN (42)) AND (`tt_content`.`pid` IN (43)) AND (tt_content.cruser_id=5)',
             ],
         ];
     }
index 1ce40c3..685b76b 100644 (file)
@@ -3731,7 +3731,7 @@ class ContentObjectRendererTest extends UnitTestCase
         foreach (array_unique($processors) as $processor) {
             $count += 1;
             try {
-                $conf = [$processor => '', $processor . '.' => []];
+                $conf = [$processor => '', $processor . '.' => ['table' => 'tt_content']];
                 $method = 'stdWrap_' . $processor;
                 $this->subject->$method('', $conf);
             } catch (\Exception $e) {
index a642287..f626499 100644 (file)
@@ -13,6 +13,7 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\ContentObject\Menu;
  *
  * The TYPO3 project - inspiring people to share!
  */
+use Doctrine\DBAL\Driver\Statement;
 
 /**
  * Test case
@@ -32,7 +33,6 @@ class AbstractMenuContentObjectTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
     {
         $proxyClassName = $this->buildAccessibleProxy(\TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject::class);
         $this->subject = $this->getMockForAbstractClass($proxyClassName);
-        $GLOBALS['TYPO3_DB'] = $this->getMockBuilder(\TYPO3\CMS\Core\Database\DatabaseConnection::class)->getMock();
         $GLOBALS['TSFE'] = $this->getMockBuilder(\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::class)
             ->setConstructorArgs([$GLOBALS['TYPO3_CONF_VARS'], 1, 1])
             ->getMock();
@@ -95,12 +95,14 @@ class AbstractMenuContentObjectTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function sectionIndexReturnsOverlaidRowBasedOnTheLanguageOfTheGivenPage()
     {
+        $statementProphet = $this->prophesize(Statement::class);
+        $statementProphet->fetch()->shouldBeCalledTimes(2)->willReturn(['uid' => 0, 'header' => 'NOT_OVERLAID'], false);
+
         $this->prepareSectionIndexTest();
         $this->subject->mconf['sectionIndex.']['type'] = 'all';
         $GLOBALS['TSFE']->sys_language_contentOL = 1;
         $this->subject->sys_page->expects($this->once())->method('getPage')->will($this->returnValue(['_PAGES_OVERLAY_LANGUAGE' => 1]));
-        $this->subject->parent_cObj->expects($this->once())->method('exec_getQuery')->will($this->returnValue(1));
-        $GLOBALS['TYPO3_DB']->expects($this->exactly(2))->method('sql_fetch_assoc')->will($this->onConsecutiveCalls($this->returnValue(['uid' => 0, 'header' => 'NOT_OVERLAID']), $this->returnValue(false)));
+        $this->subject->parent_cObj->expects($this->once())->method('exec_getQuery')->willReturn($statementProphet->reveal());
         $this->subject->sys_page->expects($this->once())->method('getRecordOverlay')->will($this->returnValue(['uid' => 0, 'header' => 'OVERLAID']));
         $result = $this->subject->_call('sectionIndex', 'field');
         $this->assertEquals($result[0]['title'], 'OVERLAID');
@@ -155,11 +157,14 @@ class AbstractMenuContentObjectTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function sectionIndexFilters($expectedAmount, array $dataRow)
     {
+        $statementProphet = $this->prophesize(Statement::class);
+        $statementProphet->fetch()->willReturn($dataRow, false);
+
         $this->prepareSectionIndexTest();
         $this->subject->mconf['sectionIndex.']['type'] = 'header';
         $this->subject->sys_page->expects($this->once())->method('getPage')->will($this->returnValue([]));
-        $this->subject->parent_cObj->expects($this->once())->method('exec_getQuery')->will($this->returnValue(1));
-        $GLOBALS['TYPO3_DB']->expects($this->exactly(2))->method('sql_fetch_assoc')->will($this->onConsecutiveCalls($this->returnValue($dataRow), $this->returnValue(false)));
+        $this->subject->parent_cObj->expects($this->once())->method('exec_getQuery')
+            ->willReturn($statementProphet->reveal());
         $result = $this->subject->_call('sectionIndex', 'field');
         $this->assertCount($expectedAmount, $result);
     }
@@ -201,6 +206,9 @@ class AbstractMenuContentObjectTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function sectionIndexQueriesWithDifferentColPos($configuration, $whereClausePrefix)
     {
+        $statementProphet = $this->prophesize(Statement::class);
+        $statementProphet->fetch()->willReturn([]);
+
         $this->prepareSectionIndexTest();
         $this->subject->sys_page->expects($this->once())->method('getPage')->will($this->returnValue([]));
         $this->subject->mconf['sectionIndex.'] = $configuration;
@@ -210,7 +218,9 @@ class AbstractMenuContentObjectTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
             'languageField' => 'sys_language_uid',
             'where' => $whereClausePrefix
         ];
-        $this->subject->parent_cObj->expects($this->once())->method('exec_getQuery')->with('tt_content', $queryConfiguration)->will($this->returnValue(1));
+        $this->subject->parent_cObj->expects($this->once())->method('exec_getQuery')
+            ->with('tt_content', $queryConfiguration)
+            ->willReturn($statementProphet->reveal());
         $this->subject->_call('sectionIndex', 'field', 12);
     }