我正在努力将一个复杂的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();
但是这将返回一个结果数组 而不是返回附加子对象的对象,例如: FileReference
对象或其他附加的模型。
是否有其他方法来实现所需的结果?如果没有,我怎样才能使用户输入的数据安全化,以实现 $headline
? 我需要自己写加入表的SQL吗?
这里是一些距离计算的一部分,它应该适合你关于使用Doctrine和引用的用例。
使用Doctrine你也会得到数组的记录。如果你想要Extbase对象,请看下文。
获得TYPO3风味的Doctrine DBAL QueryBuilder。
$q = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table = 'tx_....');
$q->select(
...
'a.lat',
'a.lng'
)
->from($table /*, 'alias if you want' */);
在做这样的计算时,我是不怎么使用fluent接口的(当然也可以通过使用任何MySQL函数,以 $q->selectLiteral()
).
参数
为了防止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是 $q->getSQL()
, $q->getParameters()
)
映射到Extbase对象
$dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
$objects = $dataMapper->map(YourExtbaseModel::class, $aRes);
我建议。只有当你的对象不多 或者你有足够的内存而你又不关心性能的时候 才使用Extbase对象。大部分情况下,你应该用普通数组就可以了。
在输出到Fluid模板中时,Extbase查询构建器允许进行一点优化:通过将 \TYPO3\CMS\Extbase\Persistence\Generic\QueryResult
可使用 f:paginate
ViewHelper,它不需要实例化所有对象,而只需要实例化当前页面上显示的对象。当使用Doctrine QueryBuilder时,这种可能性是不存在的(还没有?)。所以使用Extbase模型应该是最后的手段,而不是默认的。这似乎是 "最佳实践"--但现在已经不是了,我认为。