!!!|FEATURE: Extract relation resolver to data processor

Instead of hardcoding and tight coupling of relation resolving, we now
provide a dataprocessor instead.

Therefore you need to configure resolving for each indexed type.

This resolves #149 and #147.
This commit is contained in:
Daniel Siepmann 2018-04-24 09:37:25 +02:00
parent 0d57374f25
commit dfb3f897e7
Signed by: Daniel Siepmann
GPG key ID: 33D6629915560EF4
16 changed files with 262 additions and 121 deletions

View file

@ -42,7 +42,7 @@ class Service
*
* @param array|string $configuration Either the full configuration or only the class name.
*/
public function executeDataProcessor($configuration, array $data) : array
public function executeDataProcessor($configuration, array $data, string $recordType = '') : array
{
if (is_string($configuration)) {
$configuration = [
@ -50,6 +50,10 @@ class Service
];
}
if (!isset($configuration['_table']) && $recordType !== '') {
$configuration['_table'] = $recordType;
}
return $this->objectManager->get($configuration['_typoScriptNodeValue'])
->processData($data, $configuration);
}

View file

@ -0,0 +1,101 @@
<?php
namespace Codappix\SearchCore\DataProcessing;
/*
* Copyright (C) 2018 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.
*/
use Codappix\SearchCore\Domain\Index\TcaIndexer\RelationResolver;
use Codappix\SearchCore\Domain\Index\TcaIndexer\TcaTableServiceInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
/**
* Resolves relations from TCA using RelationResolver.
*/
class TcaRelationResolvingProcessor implements ProcessorInterface
{
/**
* @var ObjectManagerInterface
*/
protected $objectManager;
/**
* @var RelationResolver
*/
protected $relationResolver;
public function __construct(
ObjectManagerInterface $objectManager,
RelationResolver $relationResolver
) {
$this->objectManager = $objectManager;
$this->relationResolver = $relationResolver;
}
/**
* @throws \InvalidArgumentException If _table is not configured.
*/
public function processData(array $record, array $configuration) : array
{
$this->initializeConfiguration($configuration);
$tcaTableService = $this->objectManager->get(
TcaTableServiceInterface::class,
$configuration['_table']
);
$processedRecord = $this->relationResolver->resolveRelationsForRecord(
$tcaTableService,
$this->getRecordToProcess($record, $configuration)
);
return array_merge($record, $processedRecord);
}
/**
* @throws \InvalidArgumentException If _table is not configured.
*/
protected function initializeConfiguration(array &$configuration)
{
if (!isset($configuration['_table'])) {
throw new \InvalidArgumentException('The configuration "_table" is mandantory.', 1524552631);
}
if (!isset($configuration['excludeFields'])) {
$configuration['excludeFields'] = '';
}
$configuration['excludeFields'] = GeneralUtility::trimExplode(',', $configuration['excludeFields'], true);
}
protected function getRecordToProcess(array $record, array $configuration) : array
{
if ($configuration['excludeFields'] === []) {
return $record;
}
$newRecord = [];
$keysToUse = array_diff(array_keys($record), $configuration['excludeFields']);
foreach ($keysToUse as $keyToUse) {
$newRecord[$keyToUse] = $record[$keyToUse];
}
return $newRecord;
}
}

View file

@ -129,7 +129,7 @@ abstract class AbstractIndexer implements IndexerInterface
{
try {
foreach ($this->configuration->get('indexing.' . $this->identifier . '.dataProcessing') as $configuration) {
$record = $this->dataProcessorService->executeDataProcessor($configuration, $record);
$record = $this->dataProcessorService->executeDataProcessor($configuration, $record, $this->identifier);
}
} catch (InvalidArgumentException $e) {
// Nothing to do.

View file

@ -60,6 +60,8 @@ class PagesIndexer extends TcaIndexer
protected function prepareRecord(array &$record)
{
parent::prepareRecord($record);
$possibleTitleFields = ['nav_title', 'tx_tqseo_pagetitle_rel', 'title'];
foreach ($possibleTitleFields as $searchTitleField) {
if (isset($record[$searchTitleField]) && trim($record[$searchTitleField])) {
@ -74,7 +76,6 @@ class PagesIndexer extends TcaIndexer
$record['content'] = $content['content'];
$record['media'] = array_values(array_unique(array_merge($record['media'], $content['images'])));
}
parent::prepareRecord($record);
}
protected function fetchContentForPage(int $uid) : array

View file

@ -33,10 +33,9 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
*/
class RelationResolver implements Singleton
{
public function resolveRelationsForRecord(TcaTableServiceInterface $service, array &$record)
public function resolveRelationsForRecord(TcaTableServiceInterface $service, array $record) : array
{
foreach (array_keys($record) as $column) {
// TODO: Define / configure fields to exclude?!
if (in_array($column, ['pid', $service->getLanguageUidColumn()])) {
$record[$column] = (int) $record[$column];
continue;
@ -62,6 +61,8 @@ class RelationResolver implements Singleton
continue;
}
}
return $record;
}
protected function resolveValue($value, array $tcaColumn)

View file

@ -54,11 +54,6 @@ class TcaTableService implements TcaTableServiceInterface
*/
protected $configuration;
/**
* @var RelationResolver
*/
protected $relationResolver;
/**
* @var \TYPO3\CMS\Core\Log\Logger
*/
@ -93,7 +88,6 @@ class TcaTableService implements TcaTableServiceInterface
*/
public function __construct(
$tableName,
RelationResolver $relationResolver,
ConfigurationContainerInterface $configuration
) {
if (!isset($GLOBALS['TCA'][$tableName])) {
@ -106,7 +100,6 @@ class TcaTableService implements TcaTableServiceInterface
$this->tableName = $tableName;
$this->tca = &$GLOBALS['TCA'][$this->tableName];
$this->configuration = $configuration;
$this->relationResolver = $relationResolver;
}
public function getTableName() : string
@ -151,8 +144,6 @@ class TcaTableService implements TcaTableServiceInterface
public function prepareRecord(array &$record)
{
$this->relationResolver->resolveRelationsForRecord($this, $record);
if (isset($record['uid']) && !isset($record['search_identifier'])) {
$record['search_identifier'] = $record['uid'];
}

View file

@ -50,11 +50,6 @@ class TcaTableService76 implements TcaTableServiceInterface
*/
protected $configuration;
/**
* @var RelationResolver
*/
protected $relationResolver;
/**
* @var \TYPO3\CMS\Core\Log\Logger
*/
@ -89,7 +84,6 @@ class TcaTableService76 implements TcaTableServiceInterface
*/
public function __construct(
$tableName,
RelationResolver $relationResolver,
ConfigurationContainerInterface $configuration
) {
if (!isset($GLOBALS['TCA'][$tableName])) {
@ -102,7 +96,6 @@ class TcaTableService76 implements TcaTableServiceInterface
$this->tableName = $tableName;
$this->tca = &$GLOBALS['TCA'][$this->tableName];
$this->configuration = $configuration;
$this->relationResolver = $relationResolver;
}
public function getTableName() : string
@ -157,8 +150,6 @@ class TcaTableService76 implements TcaTableServiceInterface
public function prepareRecord(array &$record)
{
$this->relationResolver->resolveRelationsForRecord($this, $record);
if (isset($record['uid']) && !isset($record['search_identifier'])) {
$record['search_identifier'] = $record['uid'];
}

View file

@ -5,6 +5,7 @@ Changelog
:maxdepth: 1
:glob:
changelog/20180424-149-extract-relation-resolver-to-data-processing
changelog/20180410-148-keep-sys_language_uid
changelog/20180315-134-make-conent-fields-configurable
changelog/20180309-25-provide-sys-language-uid

View file

@ -0,0 +1,24 @@
Breaking Change 149 "Extract RelationResolver to a new DataProcessor"
=====================================================================
The resolving of relation, based on TCA, is no longer done by the indexer. Instead we
now provide a DataProcessor to solve this job.
As this makes it necessary to configure the DataProcessor, this is a breaking change.
Before the resolving was done out of the box.
So why did we change that? The resolving of relations was already implemented before
we added the data processors. As the concept of data processors is far more flexible,
we try to migrate hard coupled components step by step. The benefit of this change is
basically that you can now configure the resolving of relations and far more
important, the order of execution.
Now it's possible to first copy some fields, e.g. ``starttime`` and ``endtime`` to
further fields and to resolve relations afterwards. As the copied fields are not
configured in TCA, they will be skipped. This way an integrator can keep certain
information as they are.
Also the processor is now configured as all other processors. You can now optionally
configure fields to not process.
See :issue:`149` and :issue:`147`.

View file

@ -0,0 +1,34 @@
``Codappix\SearchCore\DataProcessing\TcaRelationResolvingProcessor``
====================================================================
Will resolve relations through TCA for record.
The result will be the same as in list view of TYPO3 Backend. E.g. Check boxes will be
resolved to their label, dates will be resolved to human readable representation and
relations will be resolved to their configured labels.
Combine with CopyToProcessor or exclude certain fields to keep original value for
further processing.
Mandatory Options:
``_table``
The TCA table as found on top level of ``$GLOBALS['TCA']``.
This will auto filled for indexing through the provided indexers. Still you can
apply processors on results, where no information about the table exists anymore.
Possible Options:
``excludeFields``
Comma separated list of fields to not resolve relations for.
Example::
plugin.tx_searchcore.settings.indexing.tt_content.dataProcessing {
1 = Codappix\SearchCore\DataProcessing\TcaRelationResolvingProcessor
1 {
_table = tt_content
excludeFields = starttime, endtime
}
}

View file

@ -66,6 +66,7 @@ Available DataProcessors
/configuration/dataProcessing/CopyToProcessor
/configuration/dataProcessing/GeoPointProcessor
/configuration/dataProcessing/RemoveProcessor
/configuration/dataProcessing/TcaRelationResolvingProcessor
.. _dataprocessors_plannedDataProcessors:
@ -81,18 +82,3 @@ Planned DataProcessors
``Codappix\SearchCore\DataProcessing\ChannelProcessor``
Will add a configurable channel to the record, e.g. if you have different areas in your
website like "products" and "infos".
``Codappix\SearchCore\DataProcessing\RelationResolverProcessor``
Resolves all relations using the TCA.
This is currently done through indexer.
.. Of course you are able to provide further processors. Just implement
.. ``Codappix\SearchCore\DataProcessing\ProcessorInterface`` and use the FQCN (=Fully qualified
.. class name) as done in the examples above.
.. By implementing also the same interface as necessary for TYPO3
.. :ref:`t3tsref:cobj-fluidtemplate-properties-dataprocessing`, you are able to reuse the same code
.. also for Fluid to prepare the same record fetched from DB for your fluid.
.. Dependency injection is possible inside of processors, as we instantiate through extbase
.. ``ObjectManager``.

View file

@ -1,5 +1,5 @@
<?php
namespace Codappix\SearchCore\Tests\Indexing\TcaIndexer;
namespace Codappix\SearchCore\Tests\Functional\DataProcessing;
/*
* Copyright (C) 2016 Daniel Siepmann <coding@daniel-siepmann.de>
@ -20,13 +20,13 @@ namespace Codappix\SearchCore\Tests\Indexing\TcaIndexer;
* 02110-1301, USA.
*/
use Codappix\SearchCore\Domain\Index\TcaIndexer\TcaTableService;
use Codappix\SearchCore\DataProcessing\TcaRelationResolvingProcessor;
use Codappix\SearchCore\Tests\Functional\AbstractFunctionalTestCase;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
class RelationResolverTest extends AbstractFunctionalTestCase
class TcaRelationResolvingProcessorTest extends AbstractFunctionalTestCase
{
/**
* @test
@ -37,9 +37,8 @@ class RelationResolverTest extends AbstractFunctionalTestCase
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$table = 'sys_file';
$subject = $objectManager->get(TcaTableService::class, $table);
$record = BackendUtility::getRecord($table, 1);
$subject->prepareRecord($record);
$subject = $objectManager->get(TcaRelationResolvingProcessor::class);
$record = $subject->processData(BackendUtility::getRecord($table, 1), ['_table' => $table]);
$this->assertEquals(
[
@ -60,9 +59,8 @@ class RelationResolverTest extends AbstractFunctionalTestCase
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$table = 'tt_content';
$subject = $objectManager->get(TcaTableService::class, $table);
$record = BackendUtility::getRecord($table, 1);
$subject->prepareRecord($record);
$subject = $objectManager->get(TcaRelationResolvingProcessor::class);
$record = $subject->processData(BackendUtility::getRecord($table, 1), ['_table' => $table]);
$this->assertEquals(
'Insert Plugin',
@ -80,9 +78,8 @@ class RelationResolverTest extends AbstractFunctionalTestCase
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$table = 'tt_content';
$subject = $objectManager->get(TcaTableService::class, $table);
$record = BackendUtility::getRecord($table, 1);
$subject->prepareRecord($record);
$subject = $objectManager->get(TcaRelationResolvingProcessor::class);
$record = $subject->processData(BackendUtility::getRecord($table, 1), ['_table' => $table]);
$this->assertEquals(
[
@ -103,9 +100,8 @@ class RelationResolverTest extends AbstractFunctionalTestCase
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$table = 'tt_content';
$subject = $objectManager->get(TcaTableService::class, $table);
$record = BackendUtility::getRecord($table, 1);
$subject->prepareRecord($record);
$subject = $objectManager->get(TcaRelationResolvingProcessor::class);
$record = $subject->processData(BackendUtility::getRecord($table, 1), ['_table' => $table]);
$this->assertEquals(
[

View file

@ -22,6 +22,10 @@ plugin {
type = keyword
}
}
dataProcessing {
1 = Codappix\SearchCore\DataProcessing\TcaRelationResolvingProcessor
}
}
pages {
@ -34,6 +38,10 @@ plugin {
type = keyword
}
}
dataProcessing {
1 = Codappix\SearchCore\DataProcessing\TcaRelationResolvingProcessor
}
}
}

View file

@ -77,36 +77,4 @@ class TcaIndexerTest extends AbstractFunctionalTestCase
$objectManager->get(TcaIndexer::class, $tableService, $connection)->indexAllDocuments();
}
/**
* @test
*/
public function sysLanguageIsKept()
{
$this->importDataSet('Tests/Functional/Fixtures/Indexing/TcaIndexer/KeepSysLanguageUid.xml');
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ObjectManager::class);
$tableName = 'tt_content';
$tableService = $objectManager->get(
TcaTableServiceInterface::class,
$tableName,
$objectManager->get(RelationResolver::class),
$objectManager->get(ConfigurationContainerInterface::class)
);
$connection = $this->getMockBuilder(Elasticsearch::class)
->setMethods(['addDocuments'])
->disableOriginalConstructor()
->getMock();
$connection->expects($this->once())
->method('addDocuments')
->with(
$this->stringContains('tt_content'),
$this->callback(function ($documents) {
return isset($documents[0]['sys_language_uid']) && $documents[0]['sys_language_uid'] === 2;
})
);
$objectManager->get(TcaIndexer::class, $tableService, $connection)->indexAllDocuments();
}
}

View file

@ -6,4 +6,6 @@ if (getenv('TYPO3_VERSION') === '~7.6') {
$filePath = '.Build/vendor/typo3/cms/typo3/sysext/core/Build/UnitTestsBootstrap.php';
}
date_default_timezone_set('UTC');
require_once dirname(dirname(__DIR__)) . '/' . $filePath;

View file

@ -1,5 +1,5 @@
<?php
namespace Codappix\SearchCore\Tests\Unit\Domain\Index\TcaIndexer;
namespace Codappix\SearchCore\Tests\Unit\DataProcessing;
/*
* Copyright (C) 2018 Daniel Siepmann <coding@daniel-siepmann.de>
@ -20,21 +20,46 @@ namespace Codappix\SearchCore\Tests\Unit\Domain\Index\TcaIndexer;
* 02110-1301, USA.
*/
use Codappix\SearchCore\Domain\Index\TcaIndexer\RelationResolver;
use Codappix\SearchCore\Domain\Index\TcaIndexer\TcaTableServiceInterface;
use Codappix\SearchCore\Configuration\ConfigurationContainerInterface;
use Codappix\SearchCore\DataProcessing\TcaRelationResolvingProcessor;
use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use \TYPO3\CMS\Core\Utility\GeneralUtility;
class RelationResolverTest extends AbstractUnitTestCase
class TcaRelationResolvingProcessorTest extends AbstractUnitTestCase
{
/**
* @var RelationResolver
* @var TcaRelationResolvingProcessor
*/
protected $subject;
/**
* @var ConfigurationContainerInterface
*/
protected $configurationMock;
public function setUp()
{
parent::setUp();
$this->subject = new RelationResolver();
$this->configurationMock = $this->getMockBuilder(ConfigurationContainerInterface::class)->getMock();
GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\Container\Container::class)
->registerImplementation(
ConfigurationContainerInterface::class,
get_class($this->configurationMock)
);
$this->subject = GeneralUtility::makeInstance(ObjectManager::class)
->get(TcaRelationResolvingProcessor::class);
}
/**
* @test
*/
public function exceptionIsThrownIfTableIsNotConfigured()
{
$this->expectException(\InvalidArgumentException::class);
$this->subject->processData([], []);
}
/**
@ -42,11 +67,17 @@ class RelationResolverTest extends AbstractUnitTestCase
*/
public function sysLanguageUidZeroIsKept()
{
$record = [
$originalRecord = [
'sys_language_uid' => '0',
];
$record = [
'sys_language_uid' => 0,
];
$GLOBALS['TCA'] = [
'tt_content' => [
'ctrl' => [
'languageField' => 'sys_language_uid',
],
'columns' => [
'sys_language_uid' => [
'config' => [
@ -68,19 +99,10 @@ class RelationResolverTest extends AbstractUnitTestCase
],
],
];
$tableServiceMock = $this->getMockBuilder(TcaTableServiceInterface::class)->getMock();
$tableServiceMock->expects($this->any())
->method('getTableName')
->willReturn('tt_content');
$tableServiceMock->expects($this->any())
->method('getLanguageUidColumn')
->willReturn('sys_language_uid');
$tableServiceMock->expects($this->any())
->method('getColumnConfig')
->willReturn($GLOBALS['TCA']['tt_content']['columns']['sys_language_uid']['config']);
$this->subject->resolveRelationsForRecord($tableServiceMock, $record);
$configuration = [
'_table' => 'tt_content',
];
$record = $this->subject->processData($originalRecord, $configuration);
$this->assertSame(
[
'sys_language_uid' => 0,
@ -96,7 +118,8 @@ class RelationResolverTest extends AbstractUnitTestCase
public function renderTypeInputDateTimeIsHandled()
{
$originalRecord = [
'starttime' => 0,
'endtime' => 99999999999,
'starttime' => 1523010960,
];
$record = $originalRecord;
$GLOBALS['TCA'] = [
@ -104,12 +127,24 @@ class RelationResolverTest extends AbstractUnitTestCase
'columns' => [
'starttime' => [
'config' => [
'type' => 'input',
'default' => 0,
'eval' => 'datetime',
'eval' => 'datetime,int',
'renderType' => 'inputDateTime',
],
'type' => 'input',
'exclude' => 1,
'exclude' => true,
'l10n_display' => 'defaultAsReadonly',
'l10n_mode' => 'exclude',
'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
],
'endtime' => [
'config' => [
'type' => 'input',
'default' => 0,
'eval' => 'datetime,int',
'renderType' => 'inputDateTime',
],
'exclude' => true,
'l10n_display' => 'defaultAsReadonly',
'l10n_mode' => 'exclude',
'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
@ -117,20 +152,18 @@ class RelationResolverTest extends AbstractUnitTestCase
],
],
];
$tableServiceMock = $this->getMockBuilder(TcaTableServiceInterface::class)->getMock();
$tableServiceMock->expects($this->any())
->method('getTableName')
->willReturn('tt_content');
$tableServiceMock->expects($this->any())
->method('getColumnConfig')
->willReturn($GLOBALS['TCA']['tt_content']['columns']['starttime']['config']);
$this->subject->resolveRelationsForRecord($tableServiceMock, $record);
$configuration = [
'_table' => 'tt_content',
'excludeFields' => 'starttime',
];
$record = $this->subject->processData($originalRecord, $configuration);
$this->assertSame(
$originalRecord,
[
'endtime' => '16-11-38 09:46',
'starttime' => 1523010960,
],
$record,
'TCA column configured with renderType inputDateTime was not kept as unix timestamp.'
'Exclude fields were not respected.'
);
}
}