Skip to content

Commit

Permalink
[TASK] Improve cache approach in content registration
Browse files Browse the repository at this point in the history
  • Loading branch information
NamelessCoder committed Aug 12, 2023
1 parent c00682a commit c19925d
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 73 deletions.
56 changes: 17 additions & 39 deletions Classes/Builder/ContentTypeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
use FluidTYPO3\Flux\Provider\Interfaces\RecordProviderInterface;
use FluidTYPO3\Flux\Provider\Provider;
use FluidTYPO3\Flux\Provider\ProviderInterface;
use FluidTYPO3\Flux\Service\CacheService;
use FluidTYPO3\Flux\Utility\CompatibilityRegistry;
use FluidTYPO3\Flux\Utility\ExtensionNamingUtility;
use FluidTYPO3\Flux\Utility\MiscellaneousUtility;
Expand All @@ -36,13 +35,6 @@ class ContentTypeBuilder
{
const DEFAULT_SHOWITEM = 'defaultShowItem';

protected CacheService $cacheService;

public function __construct(CacheService $cacheService)
{
$this->cacheService = $cacheService;
}

/**
* @param string $providerExtensionName
* @param string $templateFilename
Expand Down Expand Up @@ -208,30 +200,24 @@ public function registerContentType(
string $contentType,
ProviderInterface $provider
): void {
$cacheId = 'CType_' . md5($contentType . '__' . $providerExtensionName);
/** @var Form|null $form */
$form = $this->cacheService->getFromCaches($cacheId);
// Provider *must* be able to return a Form without any global configuration or specific content
// record being passed to it. We test this now to fail early if any errors happen during Form fetching.
$form = $provider->getForm(['CType' => $contentType]);
if (!$form) {
// Provider *must* be able to return a Form without any global configuration or specific content
// record being passed to it. We test this now to fail early if any errors happen during Form fetching.
$form = $provider->getForm(['CType' => $contentType]);
if (!$form) {
return;
}
try {
$form->setExtensionName($providerExtensionName);
//$this->cacheService->setInCaches($form, true, $cacheId);
} catch (\Exception $error) {
// Possible serialization error!
// Unfortunately we must do pokemon-style exception catching since serialization
// errors use the most base Exception class in PHP. So instead we check for a
// specific dispatcher in the stack trace and re-throw if not matched.
$pitcher = $error->getTrace()[0] ?? false;
if ($pitcher && ($pitcher['class'] ?? '') !== 'SplObjectStorage'
&& $pitcher['function'] !== 'serialize'
) {
throw $error;
}
return;
}
try {
$form->setExtensionName($providerExtensionName);
} catch (\Exception $error) {
// Possible serialization error!
// Unfortunately we must do pokemon-style exception catching since serialization
// errors use the most base Exception class in PHP. So instead we check for a
// specific dispatcher in the stack trace and re-throw if not matched.
$pitcher = $error->getTrace()[0] ?? false;
if ($pitcher && ($pitcher['class'] ?? '') !== 'SplObjectStorage'
&& $pitcher['function'] !== 'serialize'
) {
throw $error;
}
}

Expand All @@ -247,14 +233,6 @@ public function registerContentType(
'CType',
$providerExtensionName
);

/*
// Flush the cache entry that was generated; make sure any TypoScript overrides will take place once
// all TypoScript is finally loaded.
$this->cacheService->remove(
'viewpaths_' . ExtensionNamingUtility::getExtensionKey($providerExtensionName)
);
*/
}

protected function addIcon(Form $form, string $contentType): string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,36 @@
use FluidTYPO3\Flux\Form;
use FluidTYPO3\Flux\Provider\Provider;
use FluidTYPO3\Flux\Provider\ProviderInterface;
use FluidTYPO3\Flux\Service\CacheService;
use FluidTYPO3\Flux\Utility\ExtensionNamingUtility;
use Symfony\Component\Finder\Finder;
use TYPO3\CMS\Core\Core\ApplicationContext;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Package\PackageManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Exception;

class SpooledConfigurationApplicator
{
private const CACHE_ID_SORTINGS = 'flux_contentType_sortingValues';

private ContentTypeBuilder $contentTypeBuilder;
private ContentTypeManager $contentTypeManager;
private RequestBuilder $requestBuilder;
private PackageManager $packageManager;
private CacheService $cacheService;

public function __construct(
ContentTypeBuilder $contentTypeBuilder,
ContentTypeManager $contentTypeManager,
RequestBuilder $requestBuilder,
PackageManager $packageManager
PackageManager $packageManager,
CacheService $cacheService
) {
$this->contentTypeBuilder = $contentTypeBuilder;
$this->contentTypeManager = $contentTypeManager;
$this->requestBuilder = $requestBuilder;
$this->packageManager = $packageManager;
$this->cacheService = $cacheService;
}

public function processData(): void
Expand Down Expand Up @@ -112,7 +117,6 @@ protected function spoolQueuedContentTypeRegistrations(array $queue): void
$applicationContext = $this->getApplicationContext();
$providers = [];
foreach ($queue as $queuedRegistration) {
/** @var ProviderInterface $provider */
[
$providerExtensionName,
$templateFilename,
Expand All @@ -124,6 +128,8 @@ protected function spoolQueuedContentTypeRegistrations(array $queue): void
try {
$contentType = $contentType ?: $this->determineContentType($providerExtensionName, $templateFilename);
$defaultControllerExtensionName = 'FluidTYPO3.Flux';

/** @var ProviderInterface $provider */
$provider = $this->contentTypeBuilder->configureContentTypeFromTemplateFile(
$providerExtensionName,
$templateFilename,
Expand All @@ -145,26 +151,29 @@ protected function spoolQueuedContentTypeRegistrations(array $queue): void
}
}

$self = $this;

$backup = $GLOBALS['TYPO3_REQUEST'] ?? null;

$GLOBALS['TYPO3_REQUEST'] = $this->requestBuilder->getServerRequest();

$sortingValues = $this->resolveSortingValues($providers);
uasort(
$providers,
function (ProviderInterface $item1, ProviderInterface $item2) use ($self) {
$form1 = $item1->getForm(['CType' => $item1->getContentObjectType()]);
$form2 = $item2->getForm(['CType' => $item2->getContentObjectType()]);
return $self->resolveSortingValue($form1) <=> $self->resolveSortingValue($form2);
function (ProviderInterface $item1, ProviderInterface $item2) use ($sortingValues) {
$contentType1 = $item1->getContentObjectType();
$contentType2 = $item2->getContentObjectType();
return ($sortingValues[$contentType1] ?? 0) <=> ($sortingValues[$contentType2] ?? 0);
}
);

$providerExtensionNamesToFlush = [];

foreach ($providers as $provider) {
$contentType = $provider->getContentObjectType();
$virtualRecord = ['CType' => $contentType];
$providerExtensionName = $provider->getExtensionKey($virtualRecord);

$providerExtensionNamesToFlush[] = $providerExtensionName;

try {
$this->contentTypeBuilder->registerContentType($providerExtensionName, $contentType, $provider);
} catch (Exception $error) {
Expand All @@ -174,9 +183,39 @@ function (ProviderInterface $item1, ProviderInterface $item2) use ($self) {
}
}

// Flush the cache entry that was generated; make sure any TypoScript overrides will take place once
// all TypoScript is finally loaded.
foreach (array_unique($providerExtensionNamesToFlush) as $providerExtensionName) {
$this->cacheService->remove(
'viewpaths_' . ExtensionNamingUtility::getExtensionKey($providerExtensionName)
);
}

$GLOBALS['TYPO3_REQUEST'] = $backup;
}

/**
* @param ProviderInterface[] $providers
*/
private function resolveSortingValues(array $providers): array
{
/** @var array|false $fromCache */
$fromCache = $this->cacheService->getFromCaches(self::CACHE_ID_SORTINGS);
if ($fromCache) {
return $fromCache;
}

$sortingValues = [];
foreach ($providers as $provider) {
$contentObjectType = $provider->getContentObjectType();
$form = $provider->getForm(['CType' => $contentObjectType]);
$sortingValues[$contentObjectType] = $this->resolveSortingValue($form);
}

$this->cacheService->setInCaches($sortingValues, true, self::CACHE_ID_SORTINGS);
return $sortingValues;
}

private function resolveSortingValue(?Form $form): string
{
$sortingOptionValue = 0;
Expand All @@ -201,14 +240,4 @@ protected function getApplicationContext(): ApplicationContext
{
return Environment::getContext();
}

/**
* @codeCoverageIgnore
*/
protected function getContentTypeManager(): ContentTypeManager
{
/** @var ContentTypeManager $contentTypeManager */
$contentTypeManager = GeneralUtility::makeInstance(ContentTypeManager::class);
return $contentTypeManager;
}
}
14 changes: 2 additions & 12 deletions Tests/Unit/Builder/ContentTypeBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use FluidTYPO3\Flux\Form;
use FluidTYPO3\Flux\Provider\Provider;
use FluidTYPO3\Flux\Provider\ProviderInterface;
use FluidTYPO3\Flux\Service\CacheService;
use FluidTYPO3\Flux\Tests\Fixtures\Classes\AccessibleExtensionManagementUtility;
use FluidTYPO3\Flux\Tests\Unit\AbstractTestCase;
use FluidTYPO3\Flux\Utility\CompatibilityRegistry;
Expand All @@ -26,8 +25,6 @@
*/
class ContentTypeBuilderTest extends AbstractTestCase
{
protected CacheService $cacheService;

protected function setUp(): void
{
parent::setUp();
Expand All @@ -41,11 +38,6 @@ protected function setUp(): void
$packageManager->method('getPackage')->willReturn($package);
$packageManager->method('isPackageActive')->willReturn(true);

$this->cacheService = $this->getMockBuilder(CacheService::class)
->disableOriginalConstructor()
->onlyMethods(['setInCaches', 'getFromCaches'])
->getMock();

CompatibilityRegistry::register(ContentTypeBuilder::DEFAULT_SHOWITEM, ['8.7' => 'foo']);
AccessibleExtensionManagementUtility::setPackageManager($packageManager);
}
Expand All @@ -61,7 +53,7 @@ protected function tearDown(): void

public function testAddBoilerplateTableConfiguration(): void
{
$subject = new ContentTypeBuilder($this->cacheService);
$subject = new ContentTypeBuilder();
$subject->addBoilerplateTableConfiguration('foobar');
$this->assertNotEmpty($GLOBALS['TCA']['tt_content']['types']['foobar']);
}
Expand All @@ -71,7 +63,6 @@ public function testRegisterContentType(): void
$GLOBALS['TCA']['tt_content']['columns']['CType']['config']['items'] = [];

$subject = $this->getMockBuilder(ContentTypeBuilder::class)
->setConstructorArgs([$this->cacheService])
->onlyMethods(['createIcon'])
->getMock();
$subject->method('createIcon')->willReturn('icon');
Expand All @@ -92,7 +83,6 @@ public function testConfigureContentTypeFromTemplateFile(): void
$provider = $this->getMockBuilder(Provider::class)->disableOriginalConstructor()->getMock();
GeneralUtility::addInstance(Provider::class, $provider);
$subject = $this->getMockBuilder(ContentTypeBuilder::class)
->setConstructorArgs([$this->cacheService])
->addMethods(['dummy'])->getMock();
$result = $subject->configureContentTypeFromTemplateFile(
'FluidTYPO3.Flux',
Expand All @@ -104,7 +94,7 @@ public function testConfigureContentTypeFromTemplateFile(): void
public function testThrowsExceptionOnInvalidProviderClass(): void
{
$this->expectExceptionCode(1690816678);
(new ContentTypeBuilder($this->cacheService))->configureContentTypeFromTemplateFile(
(new ContentTypeBuilder())->configureContentTypeFromTemplateFile(
'FluidTYPO3.Flux',
'/dev/null',
\DateTime::class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use FluidTYPO3\Flux\Integration\Configuration\ConfigurationContext;
use FluidTYPO3\Flux\Integration\Configuration\SpooledConfigurationApplicator;
use FluidTYPO3\Flux\Provider\ProviderInterface;
use FluidTYPO3\Flux\Service\CacheService;
use FluidTYPO3\Flux\Tests\Fixtures\Classes\DummyConfigurationProvider;
use FluidTYPO3\Flux\Tests\Unit\AbstractTestCase;
use TYPO3\CMS\Core\Core\ApplicationContext;
Expand All @@ -34,6 +35,7 @@ class SpooledConfigurationApplicatorTest extends AbstractTestCase
private ContentTypeManager $contentTypeManager;
private RequestBuilder $requestBuilder;
private PackageManager $packageManager;
private CacheService $cacheService;

protected function setUp(): void
{
Expand All @@ -43,6 +45,11 @@ protected function setUp(): void
$provider = $this->getMockBuilder(ProviderInterface::class)->getMockForAbstractClass();
$provider->method('getForm')->willReturn($form);

$this->cacheService = $this->getMockBuilder(CacheService::class)
->disableOriginalConstructor()
->onlyMethods(['getFromCaches', 'setInCaches', 'remove'])
->getMock();

$this->contentTypeBuilder = $this->getMockBuilder(ContentTypeBuilder::class)
->onlyMethods(
[
Expand Down Expand Up @@ -90,18 +97,17 @@ protected function setUp(): void
$this->packageManager->method('getActivePackages')->willReturn([]);

$this->subject = $this->getMockBuilder(SpooledConfigurationApplicator::class)
->onlyMethods(['getApplicationContext', 'getContentTypeManager'])
->onlyMethods(['getApplicationContext'])
->setConstructorArgs(
[
$this->contentTypeBuilder,
$this->contentTypeManager,
$this->requestBuilder,
$this->packageManager,
$this->cacheService,
]
)
->getMock();
$this->subject->method('getContentTypeManager')->willReturn($this->contentTypeManager);


parent::setUp();
}
Expand Down

0 comments on commit c19925d

Please sign in to comment.