diff --git a/Build/phpstan/phpstan-baseline.neon b/Build/phpstan/phpstan-baseline.neon index 0708d0a..b369c75 100644 --- a/Build/phpstan/phpstan-baseline.neon +++ b/Build/phpstan/phpstan-baseline.neon @@ -1,7 +1,2 @@ parameters: - ignoreErrors: - - - messages: - - '#Action\(\)#' - identifier: method.unused - path: ../../Classes/Controller/MySqlReportController.php + ignoreErrors: diff --git a/Build/phpstan/phpstan.neon b/Build/phpstan/phpstan.neon index 9f5764b..03d3beb 100644 --- a/Build/phpstan/phpstan.neon +++ b/Build/phpstan/phpstan.neon @@ -1,5 +1,5 @@ includes: - - phpstan-baseline.neon + - phpstan-baseline.neon parameters: level: 6 diff --git a/Classes/Configuration/ExtConf.php b/Classes/Configuration/ExtConf.php index a76c999..85749ed 100644 --- a/Classes/Configuration/ExtConf.php +++ b/Classes/Configuration/ExtConf.php @@ -11,59 +11,64 @@ namespace StefanFroemken\Mysqlreport\Configuration; -use Psr\Log\LoggerInterface; use StefanFroemken\Mysqlreport\Traits\Typo3RequestTrait; +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationExtensionNotConfiguredException; use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExistException; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Core\Environment; -use TYPO3\CMS\Core\Utility\MathUtility; /** - * This class will streamline the values from extension settings + * This class streamlines all settings from the extension manager */ -class ExtConf +#[Autoconfigure(constructor: 'create')] +final readonly class ExtConf { use Typo3RequestTrait; - private bool $enableFrontendLogging = false; + private const EXT_KEY = 'mysqlreport'; - private bool $enableBackendLogging = false; - - private bool $activateExplainQuery = false; - - private float $slowQueryThreshold = 10.0; + private const DEFAULT_SETTINGS = [ + 'enableFrontendLogging' => false, + 'enableBackendLogging' => false, + 'activateExplainQuery' => false, + 'slowQueryThreshold' => 10.0, + ]; public function __construct( - ExtensionConfiguration $extensionConfiguration, - private readonly LoggerInterface $logger, - ) { - $extConf = []; - - try { - $extConf = (array)$extensionConfiguration->get('mysqlreport'); - } catch (ExtensionConfigurationExtensionNotConfiguredException $exception) { - $this->logger->error('No extension settings could be found for extension: mysqlreport', [ - 'exception' => $exception, - ]); - } catch (ExtensionConfigurationPathDoesNotExistException $exception) { - $this->logger->error('No extension settings could be found in TYPO3_CONF_VARS for extension: mysqlreport', [ - 'exception' => $exception, - ]); - return; - } + private bool $enableFrontendLogging = self::DEFAULT_SETTINGS['enableFrontendLogging'], + private bool $enableBackendLogging = self::DEFAULT_SETTINGS['enableBackendLogging'], + private bool $activateExplainQuery = self::DEFAULT_SETTINGS['activateExplainQuery'], + private float $slowQueryThreshold = self::DEFAULT_SETTINGS['slowQueryThreshold'], + ) {} - if ($extConf === []) { - return; - } + public static function create(ExtensionConfiguration $extensionConfiguration): self + { + $extensionSettings = self::DEFAULT_SETTINGS; - // call setter method foreach configuration entry - foreach ($extConf as $key => $value) { - $methodName = 'set' . ucfirst($key); - if (method_exists($this, $methodName)) { - $this->$methodName($value); + // Overwrite default extension settings with values from EXT_CONF + try { + $extensionSettings = array_merge( + $extensionSettings, + $extensionConfiguration->get(self::EXT_KEY), + ); + + if (is_string($extensionSettings['slowQueryThreshold'])) { + $extensionSettings['slowQueryThreshold'] = str_replace( + ',', + '.', + $extensionSettings['slowQueryThreshold'], + ); } + } catch (ExtensionConfigurationExtensionNotConfiguredException|ExtensionConfigurationPathDoesNotExistException) { } + + return new self( + enableFrontendLogging: (bool)$extensionSettings['enableFrontendLogging'], + enableBackendLogging: (bool)$extensionSettings['enableBackendLogging'], + activateExplainQuery: (bool)$extensionSettings['activateExplainQuery'], + slowQueryThreshold: (float)$extensionSettings['slowQueryThreshold'], + ); } public function isEnableFrontendLogging(): bool @@ -71,48 +76,21 @@ public function isEnableFrontendLogging(): bool return $this->enableFrontendLogging; } - public function setEnableFrontendLogging(string $enableFrontendLogging): void - { - $this->enableFrontendLogging = (bool)$enableFrontendLogging; - } - public function isEnableBackendLogging(): bool { return $this->enableBackendLogging; } - public function setEnableBackendLogging(string $enableBackendLogging): void - { - $this->enableBackendLogging = (bool)$enableBackendLogging; - } - public function isActivateExplainQuery(): bool { return $this->activateExplainQuery; } - public function setActivateExplainQuery(string $activateExplainQuery): void - { - $this->activateExplainQuery = (bool)$activateExplainQuery; - } - public function getSlowQueryThreshold(): float { return $this->slowQueryThreshold; } - public function setSlowQueryThreshold(string $slowQueryThreshold): void - { - if (MathUtility::canBeInterpretedAsFloat($slowQueryThreshold)) { - $this->slowQueryThreshold = (float)$slowQueryThreshold; - } else { - $slowQueryThreshold = str_replace(',', '.', $slowQueryThreshold); - if (MathUtility::canBeInterpretedAsFloat($slowQueryThreshold)) { - $this->slowQueryThreshold = (float)$slowQueryThreshold; - } - } - } - public function isQueryLoggingActivated(): bool { if (Environment::isCli()) { diff --git a/Classes/Controller/FileSortController.php b/Classes/Controller/FileSortController.php new file mode 100644 index 0000000..931b1f7 --- /dev/null +++ b/Classes/Controller/FileSortController.php @@ -0,0 +1,41 @@ +moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_filesort', + 'MySQL Report - Filesort', + ); + + $moduleTemplate->assign( + 'queryInformationRecords', + $this->queryInformationRepository->findQueryInformationRecordsWithFilesort(), + ); + + return $moduleTemplate->renderResponse('FileSort/Index'); + } +} diff --git a/Classes/Controller/FullTableScanController.php b/Classes/Controller/FullTableScanController.php new file mode 100644 index 0000000..f8b0cf4 --- /dev/null +++ b/Classes/Controller/FullTableScanController.php @@ -0,0 +1,41 @@ +moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_fulltablescan', + 'MySQL Report - Full Table Scan', + ); + + $moduleTemplate->assign( + 'queryInformationRecords', + $this->queryInformationRepository->findQueryInformationRecordsWithFullTableScan(), + ); + + return $moduleTemplate->renderResponse('FullTableScan/Index'); + } +} diff --git a/Classes/Controller/InnoDBController.php b/Classes/Controller/InnoDBController.php new file mode 100644 index 0000000..3285d5b --- /dev/null +++ b/Classes/Controller/InnoDBController.php @@ -0,0 +1,38 @@ +moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_innodb', + 'MySQL Report - InnoDB', + ); + + $moduleTemplate->assign('renderedInfoBoxes', $this->page->getRenderedInfoBoxes()); + + return $moduleTemplate->renderResponse('InnoDB/Index'); + } +} diff --git a/Classes/Controller/MiscController.php b/Classes/Controller/MiscController.php new file mode 100644 index 0000000..f6e5ea6 --- /dev/null +++ b/Classes/Controller/MiscController.php @@ -0,0 +1,38 @@ +moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_misc', + 'MySQL Report - Misc', + ); + + $moduleTemplate->assign('renderedInfoBoxes', $this->page->getRenderedInfoBoxes()); + + return $moduleTemplate->renderResponse('Misc/Index'); + } +} diff --git a/Classes/Controller/MySqlReportController.php b/Classes/Controller/MySqlReportController.php deleted file mode 100644 index 4690d8d..0000000 --- a/Classes/Controller/MySqlReportController.php +++ /dev/null @@ -1,170 +0,0 @@ -moduleTemplateFactory->create($request); - $this->moduleTemplateHelper->addOverviewButton($moduleTemplate->getDocHeaderComponent()->getButtonBar()); - - $queryParameters = $request->getQueryParams(); - $actionMethod = ($queryParameters['action'] ?? '') . 'Action'; - - if (method_exists($this, $actionMethod)) { - return call_user_func([$this, $actionMethod], $moduleTemplate); - } - - return $this->overviewAction($moduleTemplate); - } - - private function overviewAction(ModuleTemplate $moduleTemplate): ResponseInterface - { - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'system_mysqlreport', - 'MySqlReport Overview', - ); - - $moduleTemplate->assign('status', $this->serviceLocator->get('repository.status')->findAll()); - - return $moduleTemplate->renderResponse('MySqlReport/Overview'); - } - - private function informationAction(ModuleTemplate $moduleTemplate): ResponseInterface - { - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'system_mysqlreport', - 'MySqlReport Information', - ['action' => 'information'], - ); - - $moduleTemplate->assign( - 'renderedInfoBoxes', - $this->getPageByType('page.information')->getRenderedInfoBoxes(), - ); - - return $moduleTemplate->renderResponse('MySqlReport/Information'); - } - - private function innoDbAction(ModuleTemplate $moduleTemplate): ResponseInterface - { - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'system_mysqlreport', - 'MySqlReport InnoDB', - ['action' => 'innoDb'], - ); - - $moduleTemplate->assign( - 'renderedInfoBoxes', - $this->getPageByType('page.innodb')->getRenderedInfoBoxes(), - ); - - return $moduleTemplate->renderResponse('MySqlReport/Information'); - } - - private function threadCacheAction(ModuleTemplate $moduleTemplate): ResponseInterface - { - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'system_mysqlreport', - 'MySqlReport Thread Cache', - ['action' => 'threadCache'], - ); - - $moduleTemplate->assign( - 'renderedInfoBoxes', - $this->getPageByType('page.thread_cache')->getRenderedInfoBoxes(), - ); - - return $moduleTemplate->renderResponse('MySqlReport/Information'); - } - - private function tableCacheAction(ModuleTemplate $moduleTemplate): ResponseInterface - { - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'system_mysqlreport', - 'MySqlReport Table Cache', - ['action' => 'tableCache'], - ); - - $moduleTemplate->assign( - 'renderedInfoBoxes', - $this->getPageByType('page.table_cache')->getRenderedInfoBoxes(), - ); - - return $moduleTemplate->renderResponse('MySqlReport/Information'); - } - - private function queryCacheAction(ModuleTemplate $moduleTemplate): ResponseInterface - { - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'system_mysqlreport', - 'MySqlReport Query Cache', - ['action' => 'queryCache'], - ); - - $moduleTemplate->assign( - 'renderedInfoBoxes', - $this->getPageByType('page.query_cache')->getRenderedInfoBoxes(), - ); - - return $moduleTemplate->renderResponse('MySqlReport/Information'); - } - - private function miscAction(ModuleTemplate $moduleTemplate): ResponseInterface - { - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'system_mysqlreport', - 'MySqlReport Misc', - ['action' => 'misc'], - ); - - $moduleTemplate->assign( - 'renderedInfoBoxes', - $this->getPageByType('page.misc')->getRenderedInfoBoxes(), - ); - - return $moduleTemplate->renderResponse('MySqlReport/Information'); - } - - private function getPageByType(string $type): Page - { - return $this->serviceLocator->get($type); - } -} diff --git a/Classes/Controller/ProfileController.php b/Classes/Controller/ProfileController.php index ec22156..1682b5c 100644 --- a/Classes/Controller/ProfileController.php +++ b/Classes/Controller/ProfileController.php @@ -12,32 +12,28 @@ namespace StefanFroemken\Mysqlreport\Controller; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; use StefanFroemken\Mysqlreport\Domain\Repository\QueryInformationRepository; use StefanFroemken\Mysqlreport\Helper\DownloadHelper; -use StefanFroemken\Mysqlreport\Helper\ModuleTemplateHelper; use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; /** * Controller to show and analyze all queries of a request */ -readonly class ProfileController +class ProfileController extends ActionController { public function __construct( private QueryInformationRepository $queryInformationRepository, private ModuleTemplateFactory $moduleTemplateFactory, - private ModuleTemplateHelper $moduleTemplateHelper, private DownloadHelper $downloadHelper, ) {} - public function listAction(ServerRequestInterface $request): ResponseInterface + public function listAction(): ResponseInterface { - $moduleTemplate = $this->moduleTemplateFactory->create($request); - $this->moduleTemplateHelper->addOverviewButton($moduleTemplate->getDocHeaderComponent()->getButtonBar()); - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'mysqlreport_profile_list', - 'MySqlReport Profiles', + $moduleTemplate = $this->moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_profile', + 'MySQL Report - List Profiles', ); $moduleTemplate->assign('queryInformationRecords', $this->queryInformationRepository->findQueryInformationRecordsForCall()); @@ -45,98 +41,89 @@ public function listAction(ServerRequestInterface $request): ResponseInterface return $moduleTemplate->renderResponse('Profile/List'); } - public function showAction(ServerRequestInterface $request): ResponseInterface + public function showAction(string $uniqueIdentifier): ResponseInterface { - $moduleTemplate = $this->moduleTemplateFactory->create($request); - $this->moduleTemplateHelper->addOverviewButton($moduleTemplate->getDocHeaderComponent()->getButtonBar()); - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'mysqlreport_profile_show', - 'MySqlReport Show Profile', + $moduleTemplate = $this->moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_profile', + 'MySQL Report - Show Profile', ); - $queryParameters = $request->getQueryParams(); - $uniqueIdentifier = $queryParameters['uniqueIdentifier'] ?? ''; - $moduleTemplate->assignMultiple([ 'uniqueIdentifier' => $uniqueIdentifier, - 'profileTypes' => $this->queryInformationRepository->getQueryInformationRecordsByUniqueIdentifier($uniqueIdentifier), + 'profileTypes' => $this->queryInformationRepository->getQueryInformationRecordsByUniqueIdentifier( + $uniqueIdentifier, + ), ]); return $moduleTemplate->renderResponse('Profile/Show'); } - public function queryTypeAction(ServerRequestInterface $request): ResponseInterface + public function queryTypeAction(string $uniqueIdentifier, string $queryType): ResponseInterface { - $moduleTemplate = $this->moduleTemplateFactory->create($request); - $this->moduleTemplateHelper->addOverviewButton($moduleTemplate->getDocHeaderComponent()->getButtonBar()); - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'mysqlreport_profile_querytype', - 'MySqlReport Show Profile', + $moduleTemplate = $this->moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_profile', + 'MySQL Report - Show Query Type', ); - $queryParameters = $request->getQueryParams(); - $uniqueIdentifier = $queryParameters['uniqueIdentifier'] ?? ''; - $queryType = $queryParameters['queryType'] ?? ''; - $moduleTemplate->assignMultiple([ 'uniqueIdentifier' => $uniqueIdentifier, 'queryType' => $queryType, - 'queryInformationRecords' => $this->queryInformationRepository->getQueryInformationRecordsByQueryType($uniqueIdentifier, $queryType), + 'queryInformationRecords' => $this->queryInformationRepository->getQueryInformationRecordsByQueryType( + $uniqueIdentifier, + $queryType, + ), ]); return $moduleTemplate->renderResponse('Profile/QueryType'); } - public function infoAction(ServerRequestInterface $request): ResponseInterface + public function infoAction(int $uid, string $prevController = ''): ResponseInterface { - $moduleTemplate = $this->moduleTemplateFactory->create($request); - $this->moduleTemplateHelper->addOverviewButton($moduleTemplate->getDocHeaderComponent()->getButtonBar()); - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'mysqlreport_profile_info', - 'MySqlReport Show Query Information', + $moduleTemplate = $this->moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_profile', + 'MySQL Report - Show Query Information', ); - $queryParameters = $request->getQueryParams(); - $uid = (int)($queryParameters['uid'] ?? 0); - $queryInformationRecord = $this->queryInformationRepository->getQueryInformationRecordByUid($uid); $queryInformationRecord['explain'] = unserialize($queryInformationRecord['explain_query'], ['allowed_classes' => false]); - $moduleTemplate->assign('queryInformationRecord', $queryInformationRecord); + $moduleTemplate->assignMultiple([ + 'queryInformationRecord' => $queryInformationRecord, + 'prevController' => $prevController, + ]); return $moduleTemplate->renderResponse('Profile/Info'); } - public function profilingAction(ServerRequestInterface $request): ResponseInterface + public function queryProfilingAction(int $uid, string $prevController = ''): ResponseInterface { - $moduleTemplate = $this->moduleTemplateFactory->create($request); - $this->moduleTemplateHelper->addOverviewButton($moduleTemplate->getDocHeaderComponent()->getButtonBar()); - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'mysqlreport_profile_profiling', - 'MySqlReport Show Query Profiling', + $moduleTemplate = $this->moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_profile', + 'MySQL Report - Show Query Profiling', ); - $queryParameters = $request->getQueryParams(); - $queryInformationRecord = $this->queryInformationRepository->getQueryInformationRecordByUid((int)($queryParameters['uid'] ?? 0)); + $queryInformationRecord = $this->queryInformationRepository->getQueryInformationRecordByUid($uid); - $moduleTemplate->assign('queryInformationRecord', $queryInformationRecord); - $moduleTemplate->assign('profiling', $this->queryInformationRepository->getQueryProfiling($queryInformationRecord)); + $moduleTemplate->assignMultiple([ + 'queryInformationRecord' => $queryInformationRecord, + 'profiling' => $this->queryInformationRepository->getQueryProfiling($queryInformationRecord), + 'prevController' => $prevController, + ]); return $moduleTemplate->renderResponse('Profile/QueryProfiling'); } - public function downloadAction(ServerRequestInterface $request): ResponseInterface + public function downloadAction(string $uniqueIdentifier, string $downloadType): ResponseInterface { - $queryParameters = $request->getQueryParams(); - - if (empty($queryParameters['downloadType'])) { + if ($downloadType === '') { throw new \RuntimeException('downloadType was not given in request', 1673904554); } - if (!in_array($queryParameters['downloadType'], ['csv', 'json'], true)) { + + if (!in_array($downloadType, ['csv', 'json'], true)) { throw new \RuntimeException('Given downloadType is not allowed', 1673904777); } @@ -150,11 +137,11 @@ public function downloadAction(ServerRequestInterface $request): ResponseInterfa ]; $records = $this->queryInformationRepository->getQueryInformationRecordsForDownloadByUniqueIdentifier( - $queryParameters['uniqueIdentifier'] ?? '', + $uniqueIdentifier, array_keys($columns), ); - if ($queryParameters['downloadType'] === 'csv') { + if ($downloadType === 'csv') { return $this->downloadAsCsv(array_values($columns), $records); } diff --git a/Classes/Controller/QueryCacheController.php b/Classes/Controller/QueryCacheController.php new file mode 100644 index 0000000..30fd729 --- /dev/null +++ b/Classes/Controller/QueryCacheController.php @@ -0,0 +1,38 @@ +moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_querycache', + 'MySQL Report - Query Cache', + ); + + $moduleTemplate->assign('renderedInfoBoxes', $this->page->getRenderedInfoBoxes()); + + return $moduleTemplate->renderResponse('QueryCache/Index'); + } +} diff --git a/Classes/Controller/QueryController.php b/Classes/Controller/QueryController.php deleted file mode 100644 index a874b95..0000000 --- a/Classes/Controller/QueryController.php +++ /dev/null @@ -1,100 +0,0 @@ -moduleTemplateFactory->create($request); - $this->moduleTemplateHelper->addOverviewButton($moduleTemplate->getDocHeaderComponent()->getButtonBar()); - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'mysqlreport_query_filesort', - 'MySqlReport Filesort', - ); - - $moduleTemplate->assign('queryInformationRecords', $this->queryInformationRepository->findQueryInformationRecordsWithFilesort()); - - return $moduleTemplate->renderResponse('Query/Filesort'); - } - - public function fullTableScanAction(ServerRequestInterface $request): ResponseInterface - { - $moduleTemplate = $this->moduleTemplateFactory->create($request); - $this->moduleTemplateHelper->addOverviewButton($moduleTemplate->getDocHeaderComponent()->getButtonBar()); - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'mysqlreport_query_fulltablescan', - 'MySqlReport Full Table Scan', - ); - - $moduleTemplate->assign('queryInformationRecords', $this->queryInformationRepository->findQueryInformationRecordsWithFullTableScan()); - - return $moduleTemplate->renderResponse('Query/FullTableScan'); - } - - public function slowQueryAction(ServerRequestInterface $request): ResponseInterface - { - $moduleTemplate = $this->moduleTemplateFactory->create($request); - $this->moduleTemplateHelper->addOverviewButton($moduleTemplate->getDocHeaderComponent()->getButtonBar()); - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'mysqlreport_query_slowquery', - 'MySqlReport Slow Query', - ); - - $moduleTemplate->assign('queryInformationRecords', $this->queryInformationRepository->findQueryInformationRecordsWithSlowQueries()); - $moduleTemplate->assign('slowQueryTime', $this->extConf->getSlowQueryThreshold()); - - return $moduleTemplate->renderResponse('Query/SlowQuery'); - } - - public function profileInfoAction(ServerRequestInterface $request): ResponseInterface - { - $moduleTemplate = $this->moduleTemplateFactory->create($request); - $this->moduleTemplateHelper->addOverviewButton($moduleTemplate->getDocHeaderComponent()->getButtonBar()); - $this->moduleTemplateHelper->addShortcutButton( - $moduleTemplate->getDocHeaderComponent()->getButtonBar(), - 'mysqlreport_query_profileinfo', - 'MySqlReport Profile Info', - ); - - $queryParameters = $request->getQueryParams(); - - $queryInformationRecord = $this->queryInformationRepository->getQueryInformationRecordByUid((int)($queryParameters['uid'] ?? 0)); - $queryInformationRecord['explain'] = unserialize($queryInformationRecord['explain_query'], ['allowed_classes' => false]); - - $moduleTemplate->assign('queryInformationRecord', $queryInformationRecord); - $moduleTemplate->assign('profiling', $this->queryInformationRepository->getQueryProfiling($queryInformationRecord)); - $moduleTemplate->assign('prevRouteIdentifier', $queryParameters['prevRouteIdentifier'] ?? ''); - - return $moduleTemplate->renderResponse('Query/ProfileInfo'); - } -} diff --git a/Classes/Controller/SlowQueryController.php b/Classes/Controller/SlowQueryController.php new file mode 100644 index 0000000..88d6076 --- /dev/null +++ b/Classes/Controller/SlowQueryController.php @@ -0,0 +1,43 @@ +moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_slowquery', + 'MySQL Report - Slow Query', + ); + + $moduleTemplate->assignMultiple([ + 'queryInformationRecords' => $this->queryInformationRepository->findQueryInformationRecordsWithSlowQueries(), + 'slowQueryTime' => $this->extConf->getSlowQueryThreshold(), + ]); + + return $moduleTemplate->renderResponse('SlowQuery/Index'); + } +} diff --git a/Classes/Controller/StatusController.php b/Classes/Controller/StatusController.php new file mode 100644 index 0000000..8ef6bbc --- /dev/null +++ b/Classes/Controller/StatusController.php @@ -0,0 +1,38 @@ +moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_status', + 'MySQL Report - Status', + ); + + $moduleTemplate->assign('renderedInfoBoxes', $this->page->getRenderedInfoBoxes()); + + return $moduleTemplate->renderResponse('Status/Index'); + } +} diff --git a/Classes/Controller/TableCacheController.php b/Classes/Controller/TableCacheController.php new file mode 100644 index 0000000..e224752 --- /dev/null +++ b/Classes/Controller/TableCacheController.php @@ -0,0 +1,38 @@ +moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_tablecache', + 'MySQL Report - Table Cache', + ); + + $moduleTemplate->assign('renderedInfoBoxes', $this->page->getRenderedInfoBoxes()); + + return $moduleTemplate->renderResponse('TableCache/Index'); + } +} diff --git a/Classes/Controller/ThreadCacheController.php b/Classes/Controller/ThreadCacheController.php new file mode 100644 index 0000000..450d05e --- /dev/null +++ b/Classes/Controller/ThreadCacheController.php @@ -0,0 +1,38 @@ +moduleTemplateFactory->create($this->request); + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'mysqlreport_threadcache', + 'MySQL Report - Thread Cache', + ); + + $moduleTemplate->assign('renderedInfoBoxes', $this->page->getRenderedInfoBoxes()); + + return $moduleTemplate->renderResponse('ThreadCache/Index'); + } +} diff --git a/Classes/DependencyInjection/DashboardPass.php b/Classes/DependencyInjection/DashboardPass.php index 459d7f6..417fd5f 100644 --- a/Classes/DependencyInjection/DashboardPass.php +++ b/Classes/DependencyInjection/DashboardPass.php @@ -30,14 +30,14 @@ public function __construct(string $tagName) } /** - * Start removing registered dashboard widgets, if EXT:dashboard is not installed + * Start removing registered dashboard widgets if EXT:dashboard is not installed * * This CompilerPass was called at a very early state, where $container was not injected into * GeneralUtility for makeInstance usage and ExtensionManagementUtility could not be used - * as PackageManager was not injected into that class already. We also can not use the container itself, as the + * as PackageManager was not injected into that class already. We also cannot use the container itself, as the * real PackageManger will be added to $container AFTER processing all CompilerPasses. * - * The only solution is, to check if there is a class definition of EXT:dashboard registered in given $container + * The only solution is to check if there is a class definition of EXT:dashboard registered in a given $ container */ public function process(ContainerBuilder $container): void { diff --git a/Classes/Domain/Repository/QueryInformationRepository.php b/Classes/Domain/Repository/QueryInformationRepository.php index c53e771..e783808 100644 --- a/Classes/Domain/Repository/QueryInformationRepository.php +++ b/Classes/Domain/Repository/QueryInformationRepository.php @@ -270,7 +270,7 @@ public function getQueryProfiling(array $queryInformationRecord): array while ($profilingRow = $queryResult->fetchAssociative()) { $profilingRows[] = $profilingRow; } - } catch (\Throwable | \Exception | Exception $exception) { + } catch (\Throwable $exception) { $this->logger->error('Error while executing query profiling', [ 'exception' => $exception, ]); diff --git a/Classes/Domain/Repository/StatusRepository.php b/Classes/Domain/Repository/StatusRepository.php index 6bcf954..ad30e04 100644 --- a/Classes/Domain/Repository/StatusRepository.php +++ b/Classes/Domain/Repository/StatusRepository.php @@ -23,7 +23,9 @@ { use DatabaseConnectionTrait; - public function __construct(private LoggerInterface $logger) {} + public function __construct( + private LoggerInterface $logger, + ) {} public function findAll(): StatusValues { diff --git a/Classes/Helper/DownloadHelper.php b/Classes/Helper/DownloadHelper.php index 584c2eb..5d7498d 100644 --- a/Classes/Helper/DownloadHelper.php +++ b/Classes/Helper/DownloadHelper.php @@ -36,7 +36,7 @@ public function __construct( */ public function asCSV(array $headerRow, array $records): ResponseInterface { - // Create result + // Create the result $result[] = CsvUtility::csvValues($headerRow, self::CSV_DELIMITER, self::CSV_QUOTE); foreach ($records as $record) { $result[] = CsvUtility::csvValues($record, self::CSV_DELIMITER, self::CSV_QUOTE); @@ -50,9 +50,25 @@ public function asCSV(array $headerRow, array $records): ResponseInterface */ public function asJSON(array $records): ResponseInterface { + if ($records === []) { + $json = json_encode( + [ + 'message' => 'No records for export found', + ], + ); + return $this->generateDownloadResponse($json, 'json'); + } + try { - $json = json_encode($records, JSON_THROW_ON_ERROR) ?: ''; + $json = json_encode($records, JSON_THROW_ON_ERROR); } catch (\JsonException $exception) { + if ($exception->getCode() === JSON_ERROR_UTF8) { + foreach ($records as &$record) { + $record['query'] = base64_encode($record['query']); + } + return $this->asJSON($records); + } + $this->logger->error('Error while encoding JSON in DownloadHelper', [ 'exception' => $exception, ]); diff --git a/Classes/Helper/ExplainQueryHelper.php b/Classes/Helper/ExplainQueryHelper.php index 4da5486..825f924 100644 --- a/Classes/Helper/ExplainQueryHelper.php +++ b/Classes/Helper/ExplainQueryHelper.php @@ -16,23 +16,25 @@ use StefanFroemken\Mysqlreport\Configuration\ExtConf; use StefanFroemken\Mysqlreport\Domain\Model\QueryInformation; use StefanFroemken\Mysqlreport\Traits\DatabaseConnectionTrait; -use TYPO3\CMS\Core\Utility\GeneralUtility; /** - * Helper to analyze EXPLAIN query result and add information to QueryInformation model + * Helper to analyze an EXPLAIN query result and add information to QueryInformation model */ readonly class ExplainQueryHelper { use DatabaseConnectionTrait; - public function __construct(private LoggerInterface $logger) {} + public function __construct( + private ExtConf $extConf, + private LoggerInterface $logger, + ) {} /** * @param QueryInformation $queryInformation */ public function updateQueryInformation(QueryInformation $queryInformation): void { - if (!$this->getExtConf()->isActivateExplainQuery()) { + if (!$this->extConf->isActivateExplainQuery()) { return; } @@ -79,9 +81,4 @@ private function getExplainRows(QueryInformation $queryInformation): array return $explainRows; } - - private function getExtConf(): ExtConf - { - return GeneralUtility::makeInstance(ExtConf::class); - } } diff --git a/Classes/Helper/ModuleTemplateHelper.php b/Classes/Helper/ModuleTemplateHelper.php deleted file mode 100644 index da2a3e4..0000000 --- a/Classes/Helper/ModuleTemplateHelper.php +++ /dev/null @@ -1,65 +0,0 @@ -makeLinkButton() - ->setShowLabelText(true) - ->setTitle('Overview') - ->setIcon($this->getIconFactory()->getIcon('actions-viewmode-tiles', IconSize::SMALL)) - ->setHref( - (string)$this->uriBuilder->buildUriFromRoute('system_mysqlreport'), - ); - - $buttonBar->addButton($overviewButton, ButtonBar::BUTTON_POSITION_LEFT); - } - - /** - * @param array $arguments - */ - public function addShortcutButton( - ButtonBar $buttonBar, - string $routeIdentifier, - string $displayName, - array $arguments = [], - ): void { - $shortcutButton = $buttonBar->makeShortcutButton() - ->setRouteIdentifier($routeIdentifier) - ->setDisplayName($displayName); - - if ($arguments !== []) { - $shortcutButton->setArguments($arguments); - } - - $buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT); - } - - protected function getIconFactory(): IconFactory - { - return GeneralUtility::makeInstance(IconFactory::class); - } -} diff --git a/Classes/Helper/QueryCacheHelper.php b/Classes/Helper/QueryCacheHelper.php index 39843dc..17b4e44 100644 --- a/Classes/Helper/QueryCacheHelper.php +++ b/Classes/Helper/QueryCacheHelper.php @@ -11,7 +11,7 @@ namespace StefanFroemken\Mysqlreport\Helper; -use StefanFroemken\Mysqlreport\Menu\Page; +use StefanFroemken\Mysqlreport\Domain\Model\Variables; /** * Helper with useful methods for Query Cache @@ -19,18 +19,18 @@ readonly class QueryCacheHelper { /** - * Returns true, if Query Cache is activated + * Returns true if Query Cache is activated * * @api */ - public function isQueryCacheEnabled(Page $page): bool + public function isQueryCacheEnabled(Variables $variables): bool { - return isset($page->getVariables()['query_cache_type']) + return isset($variables['query_cache_type']) && ( - strtolower($page->getVariables()['query_cache_type']) === 'on' + strtolower($variables['query_cache_type']) === 'on' || ( - is_numeric($page->getVariables()['query_cache_type']) - && (int)($page->getVariables()['query_cache_type']) === 1 + is_numeric($variables['query_cache_type']) + && (int)($variables['query_cache_type']) === 1 ) ); } diff --git a/Classes/InfoBox/AbstractInfoBox.php b/Classes/InfoBox/AbstractInfoBox.php index ebd0745..de9d523 100644 --- a/Classes/InfoBox/AbstractInfoBox.php +++ b/Classes/InfoBox/AbstractInfoBox.php @@ -11,91 +11,34 @@ namespace StefanFroemken\Mysqlreport\InfoBox; -use StefanFroemken\Mysqlreport\Enumeration\StateEnumeration; -use StefanFroemken\Mysqlreport\Menu\Page; -use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Fluid\View\StandaloneView; +use TYPO3\CMS\Core\View\ViewInterface; /** * Model with properties for panels you can see in BE module */ -abstract class AbstractInfoBox implements \SplObserver +abstract class AbstractInfoBox { - protected string $pageIdentifier = ''; + protected const TITLE = ''; - /** - * This is the title of the infobox - */ - protected string $title = ''; + abstract public function renderBody(): string; - /** - * Use addUnorderedListEntry to add new elements to