Как переписать сложный запрос в Doctrine DBAL в TYPO3? - PullRequest
0 голосов
/ 02 мая 2020

Я пытаюсь перенести сложный запрос SQL в DBAL Doctrine в 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 Ответ

1 голос
/ 02 мая 2020

Вот часть некоторых вычислений расстояния, которые должны соответствовать вашему сценарию использования в отношении использования 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:paginate ViewHelper, который не нужно создавать экземпляры всех объектов, а только те, которые показаны на текущей странице. Такой возможности нет (пока?) При использовании Doctrine QueryBuilder. Таким образом, использование моделей Extbase должно быть в крайнем случае сейчас, а не по умолчанию. Казалось бы, «лучшая практика» - это уже не так, ИМХО.

...