如何在 TYPO3 中向 Doctrine DBAL 编写复杂查询?
Howto rewrite complex query to Doctrine DBAL in TYPO3?
我正在努力将复杂的 SQL 查询迁移到 TYPO3 中的 Doctrine DBAL。我的旧存储库查询如下所示:
$enableFields = $GLOBALS['TSFE']->sys_page->enableFields('tx_test');
// calculate distance between geo coordinates
$distance = '';
if ($geoData) {
$distance = ', 3956 * 2 * ASIN( SQRT (POWER ( SIN((' . $geoData['latitude'] . ' - abs(tx_test.latitude)) * pi() / 180 / 2), 2) + COS(' . $geoData['latitude'] . ' * pi() / 180) * ' . 'COS(abs(tx_test.latitude)' .
' * pi() / 180) * POWER (SIN((' . $geoData['longitude'] .
' - tx_test.longitude)' .
' * pi() / 180 / 2), 2))) AS distance';
}
// General query
$sql = 'SELECT * ' . $distance .
' FROM tx_test ' .
' WHERE DATE_FORMAT(FROM_UNIXTIME(start), "%Y") = '.(int)$settings['flexformYear'].' AND published = 1 ' . $enableFields;
if($headline) {
$sql .= ' AND headline like '.$GLOBALS['TYPO3_DB']->quoteStr($headline).'%';
}
// ... and some more ...
$query = $this->createQuery();
$results = $query->statement($sql)->execute();
迁移到 Doctrine
现在我可以轻松删除 $GLOBALS['TYPO3_DB']->quoteStr()
并将上面代码的最后两行替换为:
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tx_test');
$results = $connection->executeQuery($sql)->fetchAll();
但是这个 returns 结果数组而不是带有附加子对象的对象,例如。 FileReference
对象或其他附加模型。
还有其他方法可以达到预期的效果吗?如果不是,我如何 secure/sanitize 用户输入 $headline
?我需要自己为联接表编写 SQL 吗?
这是一些距离计算的一部分,应该适合您关于使用 Doctrine 和引用的用例。
使用 Doctrine 你也会得到数组形式的记录。如果您需要 Extbase 对象,请参阅下文。
获取 Doctrine DBAL QueryBuilder 的 TYPO3 风格:
$q = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table = 'tx_....');
$q->select(
...
'a.lat',
'a.lng'
)
->from($table /*, 'alias if you want' */);
我在进行这样的计算时并没有经常使用流畅的界面(尽管当然可以通过使用带有 $q->selectLiteral()
的任何 MySQL 函数来做到这一点)。
参数
为了防止 SQL 注入,您应该使用 $q->quote()
/ $q->quoteIdentifier()
引用所有可能的用户输入或使用参数 $q->createNamedParameter()
.
约束示例
这只是一部分限制条件。它包含一个示例,说明如何根据搜索功能中常见的条件组合它们。
if ((float)$searchObject->radiusKm > .5) {
$_radiusOrs = [
'IF (
' . $q->quoteIdentifier('lat') . ' = 0,
100000,
12742 * ASIN(
SQRT(
POWER(
SIN(
( ' . $q->quote((float)$searchObject->lat) . '
- ABS( ' . $q->quoteIdentifier('lat') . ' )
) * 0.0087266
),
2
)
+
COS( ' . $q->quote((float)$searchObject->lng) . ' * 0.01745329 ) * COS(
ABS( ' . $q->quoteIdentifier('lat') . ' ) * 0.01745329
) * POWER(
SIN(
( ' . $q->quote((float)$searchObject->lng) . '
- ' . $q->quoteIdentifier('lng') . '
) * 0.0087266
),
2
)
)
)
) < ' . $q->quote((float)$searchObject->radiusKm),
];
$q->andWhere(
$q->expr()->orX(...$_radiusOrs)
);
}
...
$aRes = $q->execute()->fetchAll();
(如果你想调试:你得到 SQL with $q->getSQL()
, $q->getParameters()
)
映射到 Extbase 对象
$dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
$objects = $dataMapper->map(YourExtbaseModel::class, $aRes);
我建议:仅当对象很少或内存足够且不关心性能时才使用 Extbase 对象。大多数情况下,您应该尽量避免使用普通数组。
Extbase 查询构建器允许在输出到 Fluid 模板时进行一些优化:传递 \TYPO3\CMS\Extbase\Persistence\Generic\QueryResult
允许在其上使用 f:paginate
ViewHelper,它不必实例化所有对象,但仅显示在当前页面上的那些。当使用 Doctrine QueryBuilder 时,这种可能性还不存在(现在?)。因此,现在使用 Extbase 模型应该是最后的手段,而不是默认的。它似乎是 "best practice" - 恕我直言,这不再是真的。
我正在努力将复杂的 SQL 查询迁移到 TYPO3 中的 Doctrine DBAL。我的旧存储库查询如下所示:
$enableFields = $GLOBALS['TSFE']->sys_page->enableFields('tx_test');
// calculate distance between geo coordinates
$distance = '';
if ($geoData) {
$distance = ', 3956 * 2 * ASIN( SQRT (POWER ( SIN((' . $geoData['latitude'] . ' - abs(tx_test.latitude)) * pi() / 180 / 2), 2) + COS(' . $geoData['latitude'] . ' * pi() / 180) * ' . 'COS(abs(tx_test.latitude)' .
' * pi() / 180) * POWER (SIN((' . $geoData['longitude'] .
' - tx_test.longitude)' .
' * pi() / 180 / 2), 2))) AS distance';
}
// General query
$sql = 'SELECT * ' . $distance .
' FROM tx_test ' .
' WHERE DATE_FORMAT(FROM_UNIXTIME(start), "%Y") = '.(int)$settings['flexformYear'].' AND published = 1 ' . $enableFields;
if($headline) {
$sql .= ' AND headline like '.$GLOBALS['TYPO3_DB']->quoteStr($headline).'%';
}
// ... and some more ...
$query = $this->createQuery();
$results = $query->statement($sql)->execute();
迁移到 Doctrine
现在我可以轻松删除 $GLOBALS['TYPO3_DB']->quoteStr()
并将上面代码的最后两行替换为:
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tx_test');
$results = $connection->executeQuery($sql)->fetchAll();
但是这个 returns 结果数组而不是带有附加子对象的对象,例如。 FileReference
对象或其他附加模型。
还有其他方法可以达到预期的效果吗?如果不是,我如何 secure/sanitize 用户输入 $headline
?我需要自己为联接表编写 SQL 吗?
这是一些距离计算的一部分,应该适合您关于使用 Doctrine 和引用的用例。
使用 Doctrine 你也会得到数组形式的记录。如果您需要 Extbase 对象,请参阅下文。
获取 Doctrine DBAL QueryBuilder 的 TYPO3 风格:
$q = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table = 'tx_....');
$q->select(
...
'a.lat',
'a.lng'
)
->from($table /*, 'alias if you want' */);
我在进行这样的计算时并没有经常使用流畅的界面(尽管当然可以通过使用带有 $q->selectLiteral()
的任何 MySQL 函数来做到这一点)。
参数
为了防止 SQL 注入,您应该使用 $q->quote()
/ $q->quoteIdentifier()
引用所有可能的用户输入或使用参数 $q->createNamedParameter()
.
约束示例
这只是一部分限制条件。它包含一个示例,说明如何根据搜索功能中常见的条件组合它们。
if ((float)$searchObject->radiusKm > .5) {
$_radiusOrs = [
'IF (
' . $q->quoteIdentifier('lat') . ' = 0,
100000,
12742 * ASIN(
SQRT(
POWER(
SIN(
( ' . $q->quote((float)$searchObject->lat) . '
- ABS( ' . $q->quoteIdentifier('lat') . ' )
) * 0.0087266
),
2
)
+
COS( ' . $q->quote((float)$searchObject->lng) . ' * 0.01745329 ) * COS(
ABS( ' . $q->quoteIdentifier('lat') . ' ) * 0.01745329
) * POWER(
SIN(
( ' . $q->quote((float)$searchObject->lng) . '
- ' . $q->quoteIdentifier('lng') . '
) * 0.0087266
),
2
)
)
)
) < ' . $q->quote((float)$searchObject->radiusKm),
];
$q->andWhere(
$q->expr()->orX(...$_radiusOrs)
);
}
...
$aRes = $q->execute()->fetchAll();
(如果你想调试:你得到 SQL with $q->getSQL()
, $q->getParameters()
)
映射到 Extbase 对象
$dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
$objects = $dataMapper->map(YourExtbaseModel::class, $aRes);
我建议:仅当对象很少或内存足够且不关心性能时才使用 Extbase 对象。大多数情况下,您应该尽量避免使用普通数组。
Extbase 查询构建器允许在输出到 Fluid 模板时进行一些优化:传递 \TYPO3\CMS\Extbase\Persistence\Generic\QueryResult
允许在其上使用 f:paginate
ViewHelper,它不必实例化所有对象,但仅显示在当前页面上的那些。当使用 Doctrine QueryBuilder 时,这种可能性还不存在(现在?)。因此,现在使用 Extbase 模型应该是最后的手段,而不是默认的。它似乎是 "best practice" - 恕我直言,这不再是真的。