Как переписать сложный запрос в Doctrine DBAL в TYPO3?

Я изо всех сил пытаюсь перенести сложный SQL-запрос в Doctrine DBAL в TYPO3. Мой старый запрос к репозиторию выглядит так:

$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 для объединенных таблиц?

1 ответ

Решение

Вот часть некоторых расчетов расстояния, которые должны соответствовать вашему варианту использования в отношении использования 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' */);

Я не часто использую свободный интерфейс при выполнении подобных вычислений (хотя, конечно, это можно сделать, используя любую функцию 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 только в том случае, если у вас мало объектов или если у вас достаточно памяти, и вам наплевать на производительность. В основном вам следует избегать использования простых массивов.

Конструктор запросов Extbase позволил немного оптимизировать при выводе в шаблон Fluid: передача \TYPO3\CMS\Extbase\Persistence\Generic\QueryResult разрешено использовать f:paginateViewHelper, который не должен создавать экземпляры всех объектов, а только тех, которые показаны на текущей странице. Такой возможности нет (пока?) При использовании Doctrine QueryBuilder. Таким образом, использование моделей Extbase сейчас должно быть последним средством, а не по умолчанию. Казалось, что это "лучшая практика" - это уже не так, ИМХО.

Другие вопросы по тегам