TASK: Make extension more compatible

Migrate sql to doctrine.
Migrate relation resolver to use new API.
This commit is contained in:
Daniel Siepmann 2017-07-07 14:44:32 +02:00
parent fc3c12fa96
commit cf902dde83
Signed by: Daniel Siepmann
GPG key ID: 33D6629915560EF4
9 changed files with 202 additions and 133 deletions

View file

@ -0,0 +1,50 @@
<?php
namespace Codappix\SearchCore\Database\Doctrine;
/*
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
class Join
{
/**
* @var string
*/
protected $table = '';
/**
* @var string
*/
protected $condition = '';
public function __construct(string $table, string $condition)
{
$this->table = $table;
$this->condition = $condition;
}
public function getTable() : string
{
return $this->table;
}
public function getCondition() : string
{
return $this->condition;
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace Codappix\SearchCore\Database\Doctrine;
/*
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
*
*/
class Where
{
/**
* @var string
*/
protected $statement = '';
/**
* @var array
*/
protected $parameters = [];
public function __construct(string $statement, array $parameters)
{
$this->statement = $statement;
$this->parameters = $parameters;
}
public function getStatement() : string
{
return $this->statement;
}
public function getParameters() : array
{
return $this->parameters;
}
}

View file

@ -21,6 +21,8 @@ namespace Codappix\SearchCore\Domain\Index;
*/ */
use Codappix\SearchCore\Connection\ConnectionInterface; use Codappix\SearchCore\Connection\ConnectionInterface;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/** /**
* Will index the given table using configuration from TCA. * Will index the given table using configuration from TCA.
@ -51,14 +53,22 @@ class TcaIndexer extends AbstractIndexer
*/ */
protected function getRecords($offset, $limit) protected function getRecords($offset, $limit)
{ {
$records = $this->getDatabaseConnection()->exec_SELECTgetRows( $queryBuilder = $this->getDatabaseConnection()->getQueryBuilderForTable($this->tcaTableService->getTableName());
$this->tcaTableService->getFields(), $where = $this->tcaTableService->getWhereClause();
$this->tcaTableService->getTableClause(), $query = $queryBuilder->select(... $this->tcaTableService->getFields())
$this->tcaTableService->getWhereClause(), ->from($this->tcaTableService->getTableClause())
'', ->where($where->getStatement())
'', ->setParameters($where->getParameters())
(int) $offset . ',' . (int) $limit ->setFirstResult($offset)
); ->setMaxResults($limit);
foreach ($this->tcaTableService->getJoins() as $join) {
$query->from($join->getTable());
$query->andWhere($join->getCondition());
}
$records = $query->execute()->fetchAll();
if ($records === null) { if ($records === null) {
return null; return null;
} }
@ -106,7 +116,6 @@ class TcaIndexer extends AbstractIndexer
protected function getDatabaseConnection() protected function getDatabaseConnection()
{ {
return GeneralUtility::makeInstance(ConnectionPool::class) return GeneralUtility::makeInstance(ConnectionPool::class);
->getConnectionByName('Default');
} }
} }

View file

@ -42,38 +42,31 @@ class RelationResolver implements Singleton
* @param TcaTableService $service * @param TcaTableService $service
* @param array $record * @param array $record
*/ */
public function resolveRelationsForRecord(TcaTableService $service, array &$record) public function resolveRelationsForRecord(TcaTableService $service, array &$record) : void
{ {
$formData = GeneralUtility::makeInstance(
FormDataCompiler::class,
GeneralUtility::makeInstance(TcaDatabaseRecord::class)
)->compile([
'tableName' => $service->getTableName(),
'vanillaUid' => (int)$record['uid'],
'command' => 'edit',
]);
$record = $formData['databaseRow'];
foreach (array_keys($record) as $column) { foreach (array_keys($record) as $column) {
// TODO: Define / configure fields to exclude?!
if ($column === 'pid') {
continue;
}
$record[$column] = BackendUtility::getProcessedValueExtra($service->getTableName(), $column, $record[$column], 0, $record['uid']);
try { try {
$config = $service->getColumnConfig($column); $config = $service->getColumnConfig($column);
if ($this->isRelation($config)) {
$record[$column] = $this->resolveValue($record[$column], $config);
}
} catch (InvalidArgumentException $e) { } catch (InvalidArgumentException $e) {
// Column is not configured. // Column is not configured.
continue;
} }
if (! $this->isRelation($config) || !is_array($formData['processedTca']['columns'][$column])) {
continue;
}
$record[$column] = $this->resolveValue($record[$column], $formData['processedTca']['columns'][$column]);
} }
} }
/** /**
* Resolve the given value from TYPO3 API response. * Resolve the given value from TYPO3 API response.
* *
* @param string $value The value from FormEngine to resolve. * @param mixed $value The value from FormEngine to resolve.
* @param array $tcaColumn The tca config of the relation. * @param array $tcaColumn The tca config of the relation.
* *
* @return array<String>|string * @return array<String>|string
@ -83,15 +76,10 @@ class RelationResolver implements Singleton
if ($value === '' || $value === '0') { if ($value === '' || $value === '0') {
return ''; return '';
} }
if ($tcaColumn['config']['type'] === 'select') {
return $this->resolveSelectValue($value, $tcaColumn); if ($tcaColumn['type'] === 'select' || $tcaColumn['type'] === 'group') {
}
if ($tcaColumn['config']['type'] === 'group' && strpos($value, '|') !== false) {
return $this->resolveForeignDbValue($value); return $this->resolveForeignDbValue($value);
} }
if ($tcaColumn['config']['type'] === 'inline') {
return $this->resolveInlineValue($tcaColumn);
}
return ''; return '';
} }
@ -100,66 +88,24 @@ class RelationResolver implements Singleton
* @param array Column config. * @param array Column config.
* @return bool * @return bool
*/ */
protected function isRelation(array &$config) protected function isRelation(array &$config) : bool
{ {
return isset($config['foreign_table']) return isset($config['foreign_table'])
|| (isset($config['items']) && is_array($config['items'])) || (isset($config['renderType']) && $config['renderType'] !== 'selectSingle')
|| (isset($config['internal_type']) && strtolower($config['internal_type']) === 'db') || (isset($config['internal_type']) && strtolower($config['internal_type']) === 'db')
; ;
} }
/**
* Resolves internal representation of select to array of labels.
*
* @param array $value
* @param array $tcaColumn
* @return array
*/
protected function resolveSelectValue(array $values, array $tcaColumn)
{
$resolvedValues = [];
foreach ($tcaColumn['config']['items'] as $item) {
if (in_array($item[1], $values)) {
$resolvedValues[] = $item[0];
}
}
if ($tcaColumn['config']['renderType'] === 'selectSingle' || $tcaColumn['config']['maxitems'] === 1) {
return current($resolvedValues);
}
return $resolvedValues;
}
/** /**
* @param string $value * @param string $value
* *
* @return array * @return array
*/ */
protected function resolveForeignDbValue($value) protected function resolveForeignDbValue(string $value) : array
{ {
$titles = []; if ($value === 'N/A') {
return [];
foreach (explode(',', urldecode($value)) as $title) {
$titles[] = explode('|', $title)[1];
} }
return array_map('trim', explode(';', $value));
return $titles;
}
/**
* @param array $tcaColumn
* @return array
*/
protected function resolveInlineValue(array $tcaColumn)
{
$titles = [];
foreach ($tcaColumn['children'] as $selected) {
$titles[] = $selected['recordTitle'];
}
return $titles;
} }
} }

View file

@ -21,6 +21,8 @@ namespace Codappix\SearchCore\Domain\Index\TcaIndexer;
*/ */
use Codappix\SearchCore\Configuration\ConfigurationContainerInterface; use Codappix\SearchCore\Configuration\ConfigurationContainerInterface;
use Codappix\SearchCore\Database\Doctrine\Join;
use Codappix\SearchCore\Database\Doctrine\Where;
use Codappix\SearchCore\Domain\Index\IndexingException; use Codappix\SearchCore\Domain\Index\IndexingException;
use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
@ -92,7 +94,7 @@ class TcaTableService
/** /**
* @return string * @return string
*/ */
public function getTableName() public function getTableName() : string
{ {
return $this->tableName; return $this->tableName;
} }
@ -100,9 +102,9 @@ class TcaTableService
/** /**
* @return string * @return string
*/ */
public function getTableClause() public function getTableClause() : string
{ {
return $this->tableName . ' LEFT JOIN pages on ' . $this->tableName . '.pid = pages.uid'; return $this->tableName;
} }
/** /**
@ -111,7 +113,7 @@ class TcaTableService
* @param array &$records * @param array &$records
* @return void * @return void
*/ */
public function filterRecordsByRootLineBlacklist(array &$records) public function filterRecordsByRootLineBlacklist(array &$records) : void
{ {
$records = array_filter( $records = array_filter(
$records, $records,
@ -125,7 +127,7 @@ class TcaTableService
* Adjust record accordingly to configuration. * Adjust record accordingly to configuration.
* @param array &$record * @param array &$record
*/ */
public function prepareRecord(array &$record) public function prepareRecord(array &$record) : void
{ {
$this->relationResolver->resolveRelationsForRecord($this, $record); $this->relationResolver->resolveRelationsForRecord($this, $record);
@ -137,11 +139,9 @@ class TcaTableService
} }
} }
/** public function getWhereClause() : Where
* @return string
*/
public function getWhereClause()
{ {
$parameters = [];
$whereClause = $this->getSystemWhereClause(); $whereClause = $this->getSystemWhereClause();
$userDefinedWhere = $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.additionalWhereClause'); $userDefinedWhere = $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.additionalWhereClause');
if (is_string($userDefinedWhere)) { if (is_string($userDefinedWhere)) {
@ -149,16 +149,41 @@ class TcaTableService
} }
if ($this->isBlacklistedRootLineConfigured()) { if ($this->isBlacklistedRootLineConfigured()) {
$whereClause .= ' AND pages.uid NOT IN (' $parameters[':blacklistedRootLine'] = $this->getBlacklistedRootLine();
. implode(',', $this->getBlacklistedRootLine()) $whereClause .= ' AND pages.uid NOT IN (:blacklistedRootLine)'
. ')' . ' AND pages.pid NOT IN (:blacklistedRootLine)';
. ' AND pages.pid NOT IN ('
. implode(',', $this->getBlacklistedRootLine())
. ')';
} }
$this->logger->debug('Generated where clause.', [$this->tableName, $whereClause]); $this->logger->debug('Generated where clause.', [$this->tableName, $whereClause]);
return $whereClause; return new Where($whereClause, $parameters);
}
public function getFields() : array
{
$fields = array_merge(
['uid','pid'],
array_filter(
array_keys($this->tca['columns']),
function ($columnName) {
return !$this->isSystemField($columnName);
}
)
);
foreach ($fields as $key => $field) {
$fields[$key] = $this->tableName . '.' . $field;
}
$this->logger->debug('Generated fields.', [$this->tableName, $fields]);
return $fields;
return implode(', ', $fields);
}
public function getJoins() : array
{
return [
new Join('pages', 'pages.uid = ' . $this->tableName . '.pid'),
];
} }
/** /**
@ -177,34 +202,11 @@ class TcaTableService
; ;
} }
/**
* @return string
*/
public function getFields()
{
$fields = array_merge(
['uid','pid'],
array_filter(
array_keys($this->tca['columns']),
function ($columnName) {
return !$this->isSystemField($columnName);
}
)
);
foreach ($fields as $key => $field) {
$fields[$key] = $this->tableName . '.' . $field;
}
$this->logger->debug('Generated fields.', [$this->tableName, $fields]);
return implode(',', $fields);
}
/** /**
* @param string * @param string
* @return bool * @return bool
*/ */
protected function isSystemField($columnName) protected function isSystemField($columnName) : bool
{ {
$systemFields = [ $systemFields = [
// Versioning fields, // Versioning fields,
@ -228,7 +230,7 @@ class TcaTableService
* @return array * @return array
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public function getColumnConfig($columnName) public function getColumnConfig($columnName) : array
{ {
if (!isset($this->tca['columns'][$columnName])) { if (!isset($this->tca['columns'][$columnName])) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
@ -250,7 +252,7 @@ class TcaTableService
* @param array &$record * @param array &$record
* @return bool * @return bool
*/ */
protected function isRecordBlacklistedByRootline(array &$record) protected function isRecordBlacklistedByRootline(array &$record) : bool
{ {
// If no rootline exists, the record is on a unreachable page and therefore blacklisted. // If no rootline exists, the record is on a unreachable page and therefore blacklisted.
$rootline = BackendUtility::BEgetRootLine($record['pid']); $rootline = BackendUtility::BEgetRootLine($record['pid']);
@ -275,7 +277,7 @@ class TcaTableService
* *
* @return bool * @return bool
*/ */
protected function isBlackListedRootLineConfigured() protected function isBlackListedRootLineConfigured() : bool
{ {
return (bool) $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.rootLineBlacklist'); return (bool) $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.rootLineBlacklist');
} }
@ -285,7 +287,7 @@ class TcaTableService
* *
* @return array<Int> * @return array<Int>
*/ */
protected function getBlackListedRootLine() protected function getBlackListedRootLine() : array
{ {
return GeneralUtility::intExplode(',', $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.rootLineBlacklist')); return GeneralUtility::intExplode(',', $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.rootLineBlacklist'));
} }

View file

@ -22,7 +22,6 @@ functionalTests:
typo3DatabaseHost=$(typo3DatabaseHost) \ typo3DatabaseHost=$(typo3DatabaseHost) \
TYPO3_PATH_WEB=$(TYPO3_WEB_DIR) \ TYPO3_PATH_WEB=$(TYPO3_WEB_DIR) \
.Build/bin/phpunit --colors --debug -v \ .Build/bin/phpunit --colors --debug -v \
--stop-on-error \
-c Tests/Functional/FunctionalTests.xml -c Tests/Functional/FunctionalTests.xml
unitTests: unitTests:

View file

@ -55,7 +55,7 @@ class FilterTest extends AbstractFunctionalTestCase
$searchRequest->setFilter(['CType' => 'HTML']); $searchRequest->setFilter(['CType' => 'HTML']);
$result = $searchService->search($searchRequest); $result = $searchService->search($searchRequest);
$this->assertSame('5', $result->getResults()[0]['uid'], 'Did not get the expected result entry.'); $this->assertSame(5, $result->getResults()[0]['uid'], 'Did not get the expected result entry.');
$this->assertSame(1, count($result), 'Did not receive the single filtered element.'); $this->assertSame(1, count($result), 'Did not receive the single filtered element.');
} }

View file

@ -150,7 +150,7 @@ class IndexTcaTableTest extends AbstractFunctionalTestCase
['_source' => [ ['_source' => [
'uid' => '9', 'uid' => '9',
'CType' => 'Header', // Testing items 'CType' => 'Header', // Testing items
'categories' => ['Category 1', 'Category 2'], // Testing mm (with sorting) 'categories' => ['Category 2', 'Category 1'], // Testing mm
]], ]],
$response->getData()['hits']['hits'][0], $response->getData()['hits']['hits'][0],
false, false,

View file

@ -64,9 +64,14 @@ class TcaTableServiceTest extends AbstractUnitTestCase
->method('getSystemWhereClause') ->method('getSystemWhereClause')
->will($this->returnValue('1=1 AND pages.no_search = 0')); ->will($this->returnValue('1=1 AND pages.no_search = 0'));
$whereClause =$this->subject->getWhereClause();
$this->assertSame( $this->assertSame(
'1=1 AND pages.no_search = 0', '1=1 AND pages.no_search = 0',
$this->subject->getWhereClause() $whereClause->getStatement()
);
$this->assertSame(
[],
$whereClause->getParameters()
); );
} }
@ -83,9 +88,14 @@ class TcaTableServiceTest extends AbstractUnitTestCase
->method('getSystemWhereClause') ->method('getSystemWhereClause')
->will($this->returnValue('1=1 AND pages.no_search = 0')); ->will($this->returnValue('1=1 AND pages.no_search = 0'));
$whereClause = $this->subject->getWhereClause();
$this->assertSame( $this->assertSame(
'1=1 AND pages.no_search = 0 AND table.field = "someValue"', '1=1 AND pages.no_search = 0 AND table.field = "someValue"',
$this->subject->getWhereClause() $whereClause->getStatement()
);
$this->assertSame(
[],
$whereClause->getParameters()
); );
} }
} }