From f4c35d6e6e05ec912a75828cdecc739e121db1f9 Mon Sep 17 00:00:00 2001 From: Helmut Hummel Date: Fri, 4 Jan 2019 16:02:26 +0100 Subject: [PATCH] [BUGIFX] Simplify and harden cache:flush command This command needs to be as resilient as possible, therefore many dependencies from the classes have been removed, especially the dependency to Extbase DI API, which requires caching and lead to chicken/egg issues, when we changed our API. Additionally we now removed the --force flag, as in practice this options is used all the time, so we make it default and non optional to low level remove files and truncate db tables. Fixes: #770 --- .../TYPO3v87/Service/CacheLowLevelCleaner.php | 2 +- .../Command/Cache/CacheFlushCommand.php | 126 +++++++++++++++ .../Command/CacheCommandController.php | 84 +--------- .../Console/Mvc/Cli/Symfony/Application.php | 10 ++ .../Console/Service/CacheLowLevelCleaner.php | 5 +- Classes/Console/Service/CacheService.php | 144 ++---------------- Configuration/Commands.php | 12 +- Documentation/CommandReference/Index.rst | 9 -- 8 files changed, 155 insertions(+), 237 deletions(-) create mode 100644 Classes/Console/Command/Cache/CacheFlushCommand.php diff --git a/Classes/Compatibility/TYPO3v87/Service/CacheLowLevelCleaner.php b/Classes/Compatibility/TYPO3v87/Service/CacheLowLevelCleaner.php index b7cc7c917..d10aeb02f 100644 --- a/Classes/Compatibility/TYPO3v87/Service/CacheLowLevelCleaner.php +++ b/Classes/Compatibility/TYPO3v87/Service/CacheLowLevelCleaner.php @@ -45,7 +45,7 @@ public function forceFlushDatabaseCacheTables() $connection->truncate($tableName); } } - // check tables on other connections + // Check tables on other connections $remappedTables = isset($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']) ? array_keys((array)$GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']) : []; diff --git a/Classes/Console/Command/Cache/CacheFlushCommand.php b/Classes/Console/Command/Cache/CacheFlushCommand.php new file mode 100644 index 000000000..4ed9dbe99 --- /dev/null +++ b/Classes/Console/Command/Cache/CacheFlushCommand.php @@ -0,0 +1,126 @@ +setDescription('Flush all caches'); + $this->setHelp( + <<<'EOH' +Flushes TYPO3 core caches first and after that, flushes caches from extensions. +EOH + ); + + $this->setDefinition($this->createCompleteInputDefinition()); + } + + protected function createNativeDefinition(): array + { + return [ + new InputOption( + 'files-only', + null, + InputOption::VALUE_NONE, + 'Only file caches are flushed' + ), + ]; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $filesOnly = $input->getOption('files-only'); + $application = $this->getApplication(); + if (!$application instanceof Application) { + throw new \RuntimeException('Fatal error. Application is not properly initialized.', 1546617606); + } + $filesOnly = $filesOnly || !$application->isFullyCapable(); + + $io = new SymfonyStyle($input, $output); + + $lowLevelCleaner = new CacheLowLevelCleaner(); + $lowLevelCleaner->forceFlushCachesFiles(); + if ($filesOnly) { + $io->writeln('Flushed all file caches.'); + // No need to proceed, as files only flush is requested + return; + } + + $lowLevelCleaner->forceFlushDatabaseCacheTables(); + $application->boot(RunLevel::LEVEL_FULL); + + $cacheService = new CacheService(); + $cacheService->flush(); + $cacheService->flushCachesWithDataHandler(); + + $io->writeln('Flushed all caches.'); + } + + /** + * @deprecated will be removed with 6.0 + * + * @return array + */ + protected function createDeprecatedDefinition(): array + { + return [ + new InputOption( + 'force', + null, + InputOption::VALUE_NONE, + 'Cache is forcibly flushed (low level operations are performed)' + ), + new InputArgument( + 'force', + null, + 'Cache is forcibly flushed (low level operations are performed)', + false + ), + new InputArgument( + 'filesOnly', + null, + 'Only file caches are flushed', + false + ), + ]; + } + + /** + * @deprecated will be removed with 6.0 + */ + protected function handleDeprecatedArgumentsAndOptions(InputInterface $input, OutputInterface $output) + { + if ($input->getArgument('force') + || $input->getArgument('filesOnly') + || $input->getOption('files-only') + || $input->getOption('force') + ) { + $io = new SymfonyStyle($input, $output); + $io->getErrorStyle()->writeln('All options and arguments are deprecated and have no effect any more.'); + } + } +} diff --git a/Classes/Console/Command/CacheCommandController.php b/Classes/Console/Command/CacheCommandController.php index dce799e0e..7d7f301be 100644 --- a/Classes/Console/Command/CacheCommandController.php +++ b/Classes/Console/Command/CacheCommandController.php @@ -14,8 +14,6 @@ * */ -use Helhum\Typo3Console\Mvc\Cli\CommandDispatcher; -use Helhum\Typo3Console\Mvc\Cli\FailedSubProcessCommandException; use Helhum\Typo3Console\Mvc\Controller\CommandController; use Helhum\Typo3Console\Service\CacheService; use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheGroupException; @@ -30,86 +28,9 @@ class CacheCommandController extends CommandController */ private $cacheService; - /** - * @var CommandDispatcher - */ - private $commandDispatcher; - - /** - * @param CacheService $cacheService - * @param CommandDispatcher $commandDispatcher - */ - public function __construct(CacheService $cacheService, CommandDispatcher $commandDispatcher = null) + public function __construct(CacheService $cacheService) { $this->cacheService = $cacheService; - $this->commandDispatcher = $commandDispatcher ?: CommandDispatcher::createFromCommandRun(); - } - - /** - * Flush all caches - * - * Flushes TYPO3 core caches first and after that, flushes caches from extensions. - * - * @param bool $force Cache is forcibly flushed (low level operations are performed) - * @param bool $filesOnly Only file caches are flushed - * @throws FailedSubProcessCommandException - */ - public function flushCommand($force = false, $filesOnly = false) - { - $exitCode = 0; - $isApplicationFullyCapable = $this->isApplicationFullyCapable(); - if (!$isApplicationFullyCapable) { - $filesOnly = true; - } - if ($filesOnly) { - $this->cacheService->flushFileCaches($force); - try { - $this->commandDispatcher->executeCommand('cache:flushcomplete', ['--files-only']); - } catch (FailedSubProcessCommandException $e) { - if ($isApplicationFullyCapable) { - throw $e; - } - $this->output->getSymfonyConsoleOutput()->getErrorOutput()->writeln('Could not load extension configuration.'); - $this->output->getSymfonyConsoleOutput()->getErrorOutput()->writeln('Some caches might not have been flushed.'); - } - } else { - $this->cacheService->flush($force); - $this->commandDispatcher->executeCommand('cache:flushcomplete'); - } - - $this->outputLine('%slushed all %scaches.', [$force ? 'Force f' : 'F', $filesOnly ? 'file ' : '']); - $this->quit($exitCode); - } - - /** - * Check if we have all mandatory files to assume we have a fully configured / installed TYPO3 - * - * @return bool - * @deprecated can be removed once this is converted into a native Symfony command. We can use the Application API then. - */ - private function isApplicationFullyCapable(): bool - { - return file_exists(PATH_site . 'typo3conf/PackageStates.php') && file_exists(PATH_site . 'typo3conf/LocalConfiguration.php'); - } - - /** - * Called only internally in a sub process of the cache:flush command - * - * This command will then use the full TYPO3 bootstrap. - * - * @param bool $filesOnly Only file caches are flushed - * @internal - */ - public function flushCompleteCommand($filesOnly = false) - { - // Flush a second time to have extension caches and previously disabled core caches cleared when clearing not forced - if ($filesOnly) { - $this->cacheService->flushFileCaches(); - } else { - $this->cacheService->flush(); - // Also call the data handler API to cover legacy hook subscriber code - $this->cacheService->flushCachesWithDataHandler(); - } } /** @@ -118,7 +39,6 @@ public function flushCompleteCommand($filesOnly = false) * Flushes all caches in specified groups. * Valid group names are by default: * - * - all * - lowlevel * - pages * - system @@ -175,7 +95,7 @@ public function listGroupsCommand() switch (count($groups)) { case 0: - $this->outputLine('No cache group is registered.'); + $this->outputLine('No cache groups are registered.'); break; case 1: $this->outputLine('The following cache group is registered: "' . implode('", "', $groups) . '".'); diff --git a/Classes/Console/Mvc/Cli/Symfony/Application.php b/Classes/Console/Mvc/Cli/Symfony/Application.php index 2a46358d9..3f98a05f0 100644 --- a/Classes/Console/Mvc/Cli/Symfony/Application.php +++ b/Classes/Console/Mvc/Cli/Symfony/Application.php @@ -15,6 +15,7 @@ */ use Helhum\Typo3Console\Core\Booting\RunLevel; +use Helhum\Typo3Console\Core\Booting\StepFailedException; use Helhum\Typo3Console\Error\ExceptionRenderer; use Helhum\Typo3Console\Exception\CommandNotAvailableException; use Helhum\Typo3Console\Mvc\Cli\Symfony\Command\HelpCommand; @@ -79,6 +80,15 @@ public function hasErrors(): bool return $this->runLevel->getError() !== null; } + /** + * @param string $runLevel + * @throws StepFailedException + */ + public function boot(string $runLevel) + { + $this->runLevel->runSequence($runLevel); + } + /** * Whether this application is composer managed. * Can be used to enable or disable commands or arguments/ options diff --git a/Classes/Console/Service/CacheLowLevelCleaner.php b/Classes/Console/Service/CacheLowLevelCleaner.php index 58ec0d6f0..c45126b60 100644 --- a/Classes/Console/Service/CacheLowLevelCleaner.php +++ b/Classes/Console/Service/CacheLowLevelCleaner.php @@ -28,8 +28,7 @@ class CacheLowLevelCleaner */ public function forceFlushCachesFiles() { - // Delete typo3temp/Cache - GeneralUtility::flushDirectory(Environment::getVarPath() . '/cache', true, true); + GeneralUtility::flushDirectory(Environment::getVarPath() . '/cache', true); } /** @@ -46,7 +45,7 @@ public function forceFlushDatabaseCacheTables() $connection->truncate($tableName); } } - // check tables on other connections + // Check tables on other connections $remappedTables = isset($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']) ? array_keys((array)$GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']) : []; diff --git a/Classes/Console/Service/CacheService.php b/Classes/Console/Service/CacheService.php index 36803243b..49e6cbbd0 100644 --- a/Classes/Console/Service/CacheService.php +++ b/Classes/Console/Service/CacheService.php @@ -14,16 +14,8 @@ * */ -use Helhum\Typo3Console\Core\Booting\CompatibilityScripts; -use Helhum\Typo3Console\Core\Booting\Scripts; -use Helhum\Typo3Console\Service\Configuration\ConfigurationService; -use Symfony\Component\Console\Exception\RuntimeException; -use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; -use TYPO3\CMS\Core\Cache\Backend\SimpleFileBackend; use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheGroupException; -use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; -use TYPO3\CMS\Core\Core\Bootstrap; use TYPO3\CMS\Core\DataHandling\DataHandler; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -39,67 +31,28 @@ class CacheService implements SingletonInterface protected $cacheManager; /** - * @var ConfigurationService + * @var array */ - protected $configurationService; + protected $cacheConfiguration; - /** - * @var Bootstrap - */ - private $bootstrap; - - /** - * @var CacheLowLevelCleaner - */ - private $lowLevelCleaner; - - /** - * Builds the dependencies correctly - * - * @param ConfigurationService $configurationService - */ - public function __construct(ConfigurationService $configurationService, Bootstrap $bootstrap = null, CacheLowLevelCleaner $lowLevelCleaner = null) + public function __construct() { // We need a new instance here to get the real caches instead of the disabled ones $this->cacheManager = new CacheManager(); - $this->cacheManager->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']); - $this->configurationService = $configurationService; - $this->bootstrap = $bootstrap ?: Bootstrap::getInstance(); - $this->lowLevelCleaner = $lowLevelCleaner ?: new CacheLowLevelCleaner(); + $this->cacheConfiguration = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']; + $this->cacheManager->setCacheConfigurations($this->cacheConfiguration); } /** * Flushes all caches - * - * @param bool $force */ - public function flush($force = false) + public function flush() { - $this->ensureDatabaseIsInitialized(); - if ($force) { - $this->lowLevelCleaner->forceFlushCachesFiles(); - $this->lowLevelCleaner->forceFlushDatabaseCacheTables(); - } $this->cacheManager->flushCaches(); } /** - * Flushes all file based caches - * - * @param bool $force - */ - public function flushFileCaches($force = false) - { - if ($force) { - $this->lowLevelCleaner->forceFlushCachesFiles(); - } - foreach ($this->getFileCaches() as $cache) { - $cache->flush(); - } - } - - /** - * Flushes caches using the data handler. This should not be necessary any more in the future. + * Flushes caches using the data handler. * Although we trigger the cache flush API here, the real intention is to trigger * hook subscribers, so that they can do their job (flushing "other" caches when cache is flushed. * For example realurl subscribes to these hooks. @@ -111,18 +64,12 @@ public function flushFileCaches($force = false) * However if you find a valid use case for us to also call "pages" here, then please create * a pull request and describe this case. "system" or "temp_cached" will not be added however * because these are deprecated since TYPO3 8.x - * - * Besides that, this DataHandler API is probably something to be removed in TYPO3, - * so we deprecate and mark this method as internal at the same time. - * - * @deprecated Will be removed once DataHandler cache flush methods are removed in supported TYPO3 versions - * @internal */ public function flushCachesWithDataHandler() { - $this->ensureDatabaseIsInitialized(); - $this->ensureBackendUserIsInitialized(); - self::createDataHandlerFromGlobals()->clear_cacheCmd('all'); + $dataHandler = GeneralUtility::makeInstance(DataHandler::class); + $dataHandler->start([], []); + $dataHandler->clear_cacheCmd('all'); } /** @@ -134,7 +81,6 @@ public function flushCachesWithDataHandler() public function flushGroups(array $groups) { $this->ensureCacheGroupsExist($groups); - $this->ensureDatabaseIsInitialized(); foreach ($groups as $group) { $this->cacheManager->flushCachesInGroup($group); } @@ -148,7 +94,6 @@ public function flushGroups(array $groups) */ public function flushByTags(array $tags, $group = null) { - $this->ensureDatabaseIsInitialized(); foreach ($tags as $tag) { if ($group === null) { $this->cacheManager->flushCachesByTag($tag); @@ -182,35 +127,13 @@ public function flushByTagsAndGroups(array $tags, array $groups = null) public function getValidCacheGroups() { $validGroups = []; - foreach ($this->configurationService->getActive('SYS/caching/cacheConfigurations') as $cacheConfiguration) { + foreach ($this->cacheConfiguration as $cacheConfiguration) { if (isset($cacheConfiguration['groups']) && is_array($cacheConfiguration['groups'])) { - $validGroups = array_merge($validGroups, $cacheConfiguration['groups']); + $validGroups[] = $cacheConfiguration['groups']; } } - return array_unique($validGroups); - } - - /** - * @deprecated can be removed when TYPO3 8 support is removed - */ - private function ensureDatabaseIsInitialized() - { - if (!empty($GLOBALS['TYPO3_DB'])) { - // Already initialized - return; - } - CompatibilityScripts::initializeDatabaseConnection($this->bootstrap); - } - - private function ensureBackendUserIsInitialized() - { - if (!empty($GLOBALS['BE_USER'])) { - // Already initialized - return; - } - Scripts::initializePersistence($this->bootstrap); - Scripts::initializeAuthenticatedOperations($this->bootstrap); + return array_unique(array_merge(...$validGroups)); } /** @@ -226,45 +149,4 @@ private function ensureCacheGroupsExist($groups) throw new NoSuchCacheGroupException('Invalid cache groups "' . implode(', ', $invalidGroups) . '".', 1399630162); } } - - /** - * @return FrontendInterface[] - */ - private function getFileCaches() - { - $fileCaches = []; - foreach ($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'] as $identifier => $cacheConfiguration) { - if ( - isset($cacheConfiguration['backend']) - && ( - $cacheConfiguration['backend'] === SimpleFileBackend::class - || is_subclass_of($cacheConfiguration['backend'], SimpleFileBackend::class) - ) - ) { - $fileCaches[] = $this->cacheManager->getCache($identifier); - } - } - - return $fileCaches; - } - - /** - * Create a data handler instance from global state (with user being admin) - * - * @internal - * @throws RuntimeException - * @return DataHandler - */ - private static function createDataHandlerFromGlobals() - { - if (empty($GLOBALS['BE_USER']) || !$GLOBALS['BE_USER'] instanceof BackendUserAuthentication) { - throw new RuntimeException('No backend user initialized. flushCachesWithDataHandler needs fully initialized TYPO3', 1477066610); - } - $user = clone $GLOBALS['BE_USER']; - $user->admin = 1; - $dataHandler = GeneralUtility::makeInstance(DataHandler::class); - $dataHandler->start([], [], $user); - - return $dataHandler; - } } diff --git a/Configuration/Commands.php b/Configuration/Commands.php index 9c89d3d6d..f66adb4f2 100644 --- a/Configuration/Commands.php +++ b/Configuration/Commands.php @@ -56,20 +56,10 @@ ], 'cache:flush' => [ 'vendor' => 'typo3_console', - 'class' => \Helhum\Typo3Console\Mvc\Cli\Symfony\Command\DummyCommand::class, + 'class' => \Helhum\Typo3Console\Command\Cache\CacheFlushCommand::class, 'schedulable' => false, - 'controller' => \Helhum\Typo3Console\Command\CacheCommandController::class, - 'controllerCommandName' => 'flush', 'runLevel' => \Helhum\Typo3Console\Core\Booting\RunLevel::LEVEL_COMPILE, ], - 'cache:flushcomplete' => [ - 'vendor' => 'typo3_console', - 'class' => \Helhum\Typo3Console\Mvc\Cli\Symfony\Command\DummyCommand::class, - 'schedulable' => false, - 'controller' => \Helhum\Typo3Console\Command\CacheCommandController::class, - 'controllerCommandName' => 'flushComplete', - 'runLevel' => \Helhum\Typo3Console\Core\Booting\RunLevel::LEVEL_MINIMAL, - ], 'cache:flushgroups' => [ 'vendor' => 'typo3_console', 'class' => \Helhum\Typo3Console\Mvc\Cli\Symfony\Command\DummyCommand::class, diff --git a/Documentation/CommandReference/Index.rst b/Documentation/CommandReference/Index.rst index 4dc90a902..6e1e38448 100644 --- a/Documentation/CommandReference/Index.rst +++ b/Documentation/CommandReference/Index.rst @@ -319,14 +319,6 @@ Flushes TYPO3 core caches first and after that, flushes caches from extensions. Options ~~~~~~~ -`--force` - Cache is forcibly flushed (low level operations are performed) - -- Accept value: no -- Is value required: no -- Is multiple: no -- Default: false - `--files-only` Only file caches are flushed @@ -350,7 +342,6 @@ Options Flushes all caches in specified groups. Valid group names are by default: -- all - lowlevel - pages - system