├── .gitignore ├── Documentation ├── Tracing.png └── Server-Timing.png ├── .phpunit-watcher.yml ├── Classes ├── Service │ ├── RegisterShutdownFunction │ │ ├── RegisterShutdownFunctionInterface.php │ │ ├── RegisterShutdownFunction.php │ │ └── RegisterShutdownFunctionNoop.php │ ├── SentryServiceInterface.php │ ├── ConfigService.php │ └── SentryService.php ├── SqlLogging │ ├── LoggingMiddleware.php │ ├── DoctrineSqlLogger.php │ ├── LoggingDriver.php │ ├── LoggingStatement.php │ ├── LoggingConnection.php │ └── SqlLoggerCore11.php ├── Extbase │ └── XClassExtbaseDispatcher.php ├── EventListener │ ├── BootCompletedEventListener.php │ ├── FileProcessingEventListener.php │ ├── MailEventListener.php │ └── ConsoleCommandEventListener.php ├── Dto │ ├── StopWatch.php │ └── ScriptResult.php ├── DataProcessor │ ├── TrackingDataProcessor.php │ └── XClassContentDataProcessor.php ├── Middleware │ ├── AdminpanelSqlLoggingMiddleware.php │ ├── WrapMiddleware.php │ └── XClassMiddlewareDispatcher.php ├── Utility │ ├── GuzzleUtility.php │ └── TimingUtility.php └── ServiceProvider.php ├── phpstan.neon ├── infection.json ├── grumphp.yml ├── ext_emconf.php ├── ext_conf_template.txt ├── Resources └── Public │ └── Icons │ └── Extension.svg ├── phpunit.xml ├── ext_localconf.php ├── rector.php ├── composer.json ├── Configuration └── Services.yaml ├── README.md ├── .github └── workflows │ └── tasks.yml ├── Tests └── TimingUtilityTest.php ├── phpstan-baseline.neon └── .editorconfig /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | public/ 3 | vendor/ 4 | var 5 | Resources/Public/test-result 6 | .idea/ 7 | -------------------------------------------------------------------------------- /Documentation/Tracing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kanti/server-timing/HEAD/Documentation/Tracing.png -------------------------------------------------------------------------------- /Documentation/Server-Timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kanti/server-timing/HEAD/Documentation/Server-Timing.png -------------------------------------------------------------------------------- /.phpunit-watcher.yml: -------------------------------------------------------------------------------- 1 | watch: 2 | directories: 3 | - Classes/ 4 | - Tests/ 5 | fileMask: '*.php' 6 | notifications: 7 | passingTests: false 8 | failingTests: false 9 | phpunit: 10 | binaryPath: vendor/bin/phpunit 11 | arguments: '--stop-on-failure' 12 | timeout: 180 13 | -------------------------------------------------------------------------------- /Classes/Service/RegisterShutdownFunction/RegisterShutdownFunctionInterface.php: -------------------------------------------------------------------------------- 1 | callCount++; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /infection.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "vendor/infection/infection/resources/schema.json", 3 | "source": { 4 | "directories": [ 5 | "Classes" 6 | ] 7 | }, 8 | "phpUnit": { 9 | "configDir": "." 10 | }, 11 | "logs": { 12 | "text": "Resources/Public/test-result/infection.log", 13 | "html": "Resources/Public/test-result/infection.html" 14 | }, 15 | "minCoveredMsi": 96.8, 16 | "initialTestsPhpOptions": "-d pcov.enabled=1", 17 | "mutators": { 18 | "@default": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Classes/Service/SentryServiceInterface.php: -------------------------------------------------------------------------------- 1 | 'Kanti: server-timing', 8 | 'description' => 'Show timings of Database and HTTP Calls (send them to Sentry)', 9 | 'category' => 'module', 10 | 'author' => 'Matthias Vogel', 11 | 'author_email' => 'git@kanti.de', 12 | 'state' => 'stable', 13 | 'version' => InstalledVersions::getPrettyVersion('kanti/server-timing'), 14 | 'constraints' => [ 15 | 'depends' => [ 16 | 'typo3' => '11.0.0-13.4.99', 17 | ], 18 | 'conflicts' => [], 19 | 'suggests' => [], 20 | ], 21 | ]; 22 | -------------------------------------------------------------------------------- /Classes/SqlLogging/LoggingMiddleware.php: -------------------------------------------------------------------------------- 1 | =12 4 | 5 | declare(strict_types=1); 6 | 7 | namespace Kanti\ServerTiming\SqlLogging; 8 | 9 | use Doctrine\DBAL\Driver as DriverInterface; 10 | use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface; 11 | use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; 12 | 13 | if (!interface_exists(MiddlewareInterface::class)) { 14 | return; 15 | } 16 | 17 | final class LoggingMiddleware implements MiddlewareInterface 18 | { 19 | public function wrap(DriverInterface $driver): DriverInterface 20 | { 21 | return new LoggingDriver($driver, new DoctrineSqlLogger()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ext_conf_template.txt: -------------------------------------------------------------------------------- 1 | # cat=sentry; type=integer; label=StopWatch Limit 2 | stop_watch_limit = 100000 3 | 4 | # cat=sentry; type=string; label=Sentry Sample Rate between 0.0 and 1.0 (empty: keep default) 5 | sentry_sample_rate = 6 | 7 | # cat=sentry; type=string; label=Sentry CLI Sample Rate between 0.0 and 1.0 (empty: keep default) 8 | sentry_cli_sample_rate = 9 | 10 | # cat=sentry; type=integer; label=Number of timings (reduce to make header output smaller. Try out when you get regularly 502 responses). 11 | number_of_timings = 20 12 | 13 | # cat=sentry; type=integer; label=Length of description (reduce to make header output smaller. Try out when you get regularly 502 responses). 14 | length_of_description = 60 15 | -------------------------------------------------------------------------------- /Resources/Public/Icons/Extension.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Classes/SqlLogging/DoctrineSqlLogger.php: -------------------------------------------------------------------------------- 1 | =12 4 | 5 | declare(strict_types=1); 6 | 7 | namespace Kanti\ServerTiming\SqlLogging; 8 | 9 | use Kanti\ServerTiming\Dto\StopWatch; 10 | use Kanti\ServerTiming\Utility\TimingUtility; 11 | 12 | final class DoctrineSqlLogger 13 | { 14 | private ?StopWatch $stopWatch = null; 15 | 16 | public function startQuery(string $sql): void 17 | { 18 | if ($sql === 'SELECT DATABASE()') { 19 | return; 20 | } 21 | 22 | $this->stopWatch?->stopIfNot(); 23 | $this->stopWatch = TimingUtility::stopWatch('db', $sql); 24 | } 25 | 26 | public function stopQuery(): void 27 | { 28 | $this->stopWatch?->stopIfNot(); 29 | $this->stopWatch = null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Classes/SqlLogging/LoggingDriver.php: -------------------------------------------------------------------------------- 1 | =12 4 | 5 | declare(strict_types=1); 6 | 7 | namespace Kanti\ServerTiming\SqlLogging; 8 | 9 | use Doctrine\DBAL\Driver as DriverInterface; 10 | use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; 11 | 12 | if (!class_exists(AbstractDriverMiddleware::class)) { 13 | return; 14 | } 15 | 16 | final class LoggingDriver extends AbstractDriverMiddleware 17 | { 18 | public function __construct(DriverInterface $driver, private readonly DoctrineSqlLogger $logger) 19 | { 20 | parent::__construct($driver); 21 | } 22 | 23 | public function connect(array $params): DriverInterface\Connection 24 | { 25 | return new LoggingConnection(parent::connect($params), $this->logger); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Classes/Extbase/XClassExtbaseDispatcher.php: -------------------------------------------------------------------------------- 1 | getControllerObjectName()); 18 | if ($request instanceof Request) { 19 | $info .= '->' . $request->getControllerActionName(); 20 | } 21 | 22 | $stop = TimingUtility::stopWatch('extbase', $info); 23 | $response = parent::dispatch($request); 24 | $stop(); 25 | return $response; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Classes/EventListener/BootCompletedEventListener.php: -------------------------------------------------------------------------------- 1 | startTime = microtime(true); 20 | } 21 | 22 | public function getDuration(): float 23 | { 24 | $this->stopTime ??= microtime(true); 25 | return $this->stopTime - $this->startTime; 26 | } 27 | 28 | public function stop(): void 29 | { 30 | $this->stopTime = microtime(true); 31 | } 32 | 33 | public function __invoke(): void 34 | { 35 | $this->stop(); 36 | } 37 | 38 | public function stopIfNot(): void 39 | { 40 | $this->stopTime ??= microtime(true); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Classes/EventListener/FileProcessingEventListener.php: -------------------------------------------------------------------------------- 1 | getProcessedFile()->isProcessed()) { 19 | $this->stopWatch?->stopIfNot(); 20 | $this->stopWatch = TimingUtility::stopWatch('fileProcessing', $event->getProcessedFile()->getName()); 21 | } 22 | } 23 | 24 | public function after(AfterFileProcessingEvent $event): void 25 | { 26 | $this->stopWatch?->stopIfNot(); 27 | $this->stopWatch = null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Classes/SqlLogging/LoggingStatement.php: -------------------------------------------------------------------------------- 1 | =12 4 | 5 | declare(strict_types=1); 6 | 7 | namespace Kanti\ServerTiming\SqlLogging; 8 | 9 | use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; 10 | use Doctrine\DBAL\Driver\Result as ResultInterface; 11 | use Doctrine\DBAL\Driver\Statement as StatementInterface; 12 | 13 | if (!class_exists(AbstractStatementMiddleware::class)) { 14 | return; 15 | } 16 | 17 | final class LoggingStatement extends AbstractStatementMiddleware 18 | { 19 | public function __construct(StatementInterface $statement, private readonly DoctrineSqlLogger $logger, private readonly string $sql) 20 | { 21 | parent::__construct($statement); 22 | } 23 | 24 | public function execute($params = null): ResultInterface 25 | { 26 | $this->logger->startQuery($this->sql); 27 | $result = parent::execute($params); 28 | $this->logger->stopQuery(); 29 | 30 | return $result; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | Tests 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Classes 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Classes/EventListener/MailEventListener.php: -------------------------------------------------------------------------------- 1 | getMessage(); 21 | if ($message instanceof Email) { 22 | $emails = implode(', ', array_map(static fn($address): string => $address->getAddress(), $message->getTo())); 23 | $info = $message->getSubject() . ' -> ' . $emails; 24 | } 25 | 26 | $this->stopWatch?->stopIfNot(); 27 | $this->stopWatch = TimingUtility::stopWatch('mail', $info); 28 | } 29 | 30 | public function stop(AfterMailerSentMessageEvent $event): void 31 | { 32 | $this->stopWatch?->stopIfNot(); 33 | $this->stopWatch = null; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Classes/EventListener/ConsoleCommandEventListener.php: -------------------------------------------------------------------------------- 1 | stopWatches[] = TimingUtility::stopWatch('console.command', (string)$event->getCommand()?->getName()); 22 | } 23 | 24 | public function stop(ConsoleTerminateEvent $event): void 25 | { 26 | $stopWatch = array_pop($this->stopWatches); 27 | if ($stopWatch === null) { 28 | throw new Exception('No stopWatch found, did you start the command already?', 7800196394); 29 | } 30 | 31 | $stopWatch->stop(); 32 | if (!$this->stopWatches) { 33 | TimingUtility::getInstance()->shutdown(ScriptResult::fromCli($event->getExitCode())); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Classes/Dto/ScriptResult.php: -------------------------------------------------------------------------------- 1 | request; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Classes/SqlLogging/LoggingConnection.php: -------------------------------------------------------------------------------- 1 | =12 4 | 5 | declare(strict_types=1); 6 | 7 | namespace Kanti\ServerTiming\SqlLogging; 8 | 9 | use Doctrine\DBAL\Driver\Connection as ConnectionInterface; 10 | use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; 11 | use Doctrine\DBAL\Driver\Result; 12 | use Doctrine\DBAL\Driver\Statement as DriverStatement; 13 | 14 | if (!class_exists(AbstractConnectionMiddleware::class)) { 15 | return; 16 | } 17 | 18 | final class LoggingConnection extends AbstractConnectionMiddleware 19 | { 20 | public function __construct(ConnectionInterface $connection, private readonly DoctrineSqlLogger $logger) 21 | { 22 | parent::__construct($connection); 23 | } 24 | 25 | public function prepare(string $sql): DriverStatement 26 | { 27 | return new LoggingStatement(parent::prepare($sql), $this->logger, $sql); 28 | } 29 | 30 | public function query(string $sql): Result 31 | { 32 | $this->logger->startQuery($sql); 33 | $query = parent::query($sql); 34 | $this->logger->stopQuery(); 35 | 36 | return $query; 37 | } 38 | 39 | public function exec(string $sql): int 40 | { 41 | $this->logger->startQuery($sql); 42 | $query = parent::exec($sql); 43 | $this->logger->stopQuery(); 44 | 45 | return (int)$query; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ext_localconf.php: -------------------------------------------------------------------------------- 1 | =12 14 | if (version_compare((new Typo3Version())->getBranch(), '12.3', '>=')) { 15 | $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driverMiddlewares']['server_timing_logging'] = LoggingMiddleware::class; 16 | } else { 17 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][SqlLogging::class] = [ 18 | 'className' => AdminpanelSqlLoggingMiddleware::class, 19 | ]; 20 | } 21 | 22 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][Dispatcher::class] = [ 23 | 'className' => XClassExtbaseDispatcher::class, 24 | ]; 25 | 26 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][ContentDataProcessor::class] = [ 27 | 'className' => XClassContentDataProcessor::class, 28 | ]; 29 | 30 | $handler = GuzzleUtility::getHandler(); 31 | if ($handler) { 32 | $GLOBALS['TYPO3_CONF_VARS']['HTTP']['handler']['server_timing'] = $handler; 33 | } 34 | -------------------------------------------------------------------------------- /Classes/DataProcessor/TrackingDataProcessor.php: -------------------------------------------------------------------------------- 1 | */ 16 | private array $stopWatches = []; 17 | 18 | /** 19 | * @param array $contentObjectConfiguration 20 | * @param array $processorConfiguration 21 | * @param array $processedData 22 | * @return array 23 | */ 24 | public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData): array 25 | { 26 | $id = (string)$processorConfiguration['id']; 27 | $stopWatch = $this->stopWatches[$id] ?? null; 28 | $stopWatch?->stopIfNot(); 29 | 30 | if ($processorConfiguration['type'] === 'start') { 31 | $this->stopWatches[$id] = TimingUtility::stopWatch('dataP', $processorConfiguration['for'] . ' ' . $processorConfiguration['key'] . '.' . $processorConfiguration['processorOrAlias']); 32 | } else { 33 | unset($this->stopWatches[$id]); 34 | } 35 | 36 | return $processedData; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Classes/Service/ConfigService.php: -------------------------------------------------------------------------------- 1 | getConfig('stop_watch_limit') ?: self::DEFAULT_STOP_WATCH_LIMIT); 27 | } 28 | 29 | public function tracesSampleRate(): ?float 30 | { 31 | $tracesSampleRate = $this->getConfig(TimingUtility::IS_CLI ? 'sentry_cli_sample_rate' : 'sentry_sample_rate'); 32 | return $tracesSampleRate === '' ? null : (float)$tracesSampleRate; 33 | } 34 | 35 | public function enableTracing(): ?bool 36 | { 37 | $tracesSampleRate = $this->tracesSampleRate(); 38 | return $tracesSampleRate === null ? null : (bool)$tracesSampleRate; 39 | } 40 | 41 | public function getDescriptionLength(): int 42 | { 43 | return (int)($this->getConfig('length_of_description') ?: self::DEFAULT_DESCRIPTION_LENGTH); 44 | } 45 | 46 | public function getMaxNumberOfTimings(): int 47 | { 48 | return (int)($this->getConfig('number_of_timings') ?: self::DEFAULT_NUMBER_TIMINGS); 49 | } 50 | 51 | private function getConfig(string $path): string 52 | { 53 | return (string)($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['server_timing'][$path] ?? ''); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Classes/Middleware/AdminpanelSqlLoggingMiddleware.php: -------------------------------------------------------------------------------- 1 | =12 is compatible 19 | */ 20 | final class AdminpanelSqlLoggingMiddleware implements MiddlewareInterface 21 | { 22 | /** 23 | * Enable SQL Logging as early as possible to catch all queries if the admin panel is active 24 | */ 25 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface 26 | { 27 | if (StateUtility::isActivatedForUser() && StateUtility::isOpen()) { 28 | $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); 29 | $connection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); 30 | $connection->getConfiguration()->setSQLLogger( 31 | new LoggerChain( 32 | array_filter( 33 | [ 34 | GeneralUtility::makeInstance(DoctrineSqlLogger::class), 35 | $connection->getConfiguration()->getSQLLogger(), 36 | ] 37 | ) 38 | ) 39 | ); 40 | } 41 | 42 | return $handler->handle($request); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Classes/SqlLogging/SqlLoggerCore11.php: -------------------------------------------------------------------------------- 1 | =12 is compatible 16 | */ 17 | final class SqlLoggerCore11 18 | { 19 | /** 20 | * @deprecated can be removed if only TYPO3 >=12 is compatible 21 | */ 22 | public static function registerSqlLogger(): void 23 | { 24 | if (version_compare((new Typo3Version())->getBranch(), '12.3', '>=')) { 25 | return; 26 | } 27 | 28 | $doctrineSqlLogger = new DoctrineSqlLogger(); 29 | 30 | $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); 31 | $connection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); 32 | $connection->getConfiguration()->setSQLLogger( 33 | new class ($doctrineSqlLogger) implements SQLLogger { 34 | public function __construct(private readonly DoctrineSqlLogger $doctrineSqlLogger) 35 | { 36 | } 37 | 38 | public function startQuery($sql, ?array $params = null, ?array $types = null): void 39 | { 40 | $this->doctrineSqlLogger->startQuery($sql); 41 | } 42 | 43 | public function stopQuery(): void 44 | { 45 | $this->doctrineSqlLogger->stopQuery(); 46 | } 47 | } 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Classes/DataProcessor/XClassContentDataProcessor.php: -------------------------------------------------------------------------------- 1 | $configuration 15 | * @param array $variables 16 | * @return array 17 | */ 18 | public function process(ContentObjectRenderer $cObject, array $configuration, array $variables) 19 | { 20 | $processors = $configuration['dataProcessing.'] ?? []; 21 | if (!$processors) { 22 | return $variables; 23 | } 24 | 25 | $processorKeys = ArrayUtility::filterAndSortByNumericKeys($processors); 26 | $index = 1; 27 | $newDataProcessing = []; 28 | foreach ($processorKeys as $key) { 29 | $processorClassOrAlias = $processors[$key]; 30 | 31 | $uniqId = uniqId(); 32 | 33 | $newDataProcessing[$index] = TrackingDataProcessor::class; 34 | $newDataProcessing[$index . '.'] = ['key' => $key, 'processorOrAlias' => $processorClassOrAlias, 'type' => 'start', 'id' => $uniqId, 'for' => $cObject->getCurrentTable() . ':' . $cObject->data['uid']]; 35 | $index++; 36 | $newDataProcessing[$index] = $processorClassOrAlias; 37 | $newDataProcessing[$index . '.'] = $processors[$key . '.'] ?? []; 38 | $index++; 39 | $newDataProcessing[$index] = TrackingDataProcessor::class; 40 | $newDataProcessing[$index . '.'] = ['type' => 'stop', 'id' => $uniqId]; 41 | $index++; 42 | } 43 | 44 | $configuration['dataProcessing.'] = $newDataProcessing; 45 | 46 | return parent::process($cObject, $configuration, $variables); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Classes/Middleware/WrapMiddleware.php: -------------------------------------------------------------------------------- 1 | isFirst = true; 31 | } 32 | 33 | public function handle(ServerRequestInterface $request): ResponseInterface 34 | { 35 | self::$middlewareIn?->stopIfNot(); 36 | self::$middlewareIn = TimingUtility::stopWatch($this->isKernel ? 'requestHandler' : 'middleware.in', $this->info); 37 | 38 | if ($this->isKernel) { 39 | $request->getAttribute('middleware.in.total')?->stop(); 40 | } 41 | 42 | $response = $this->requestHandler->handle($request); 43 | 44 | if ($this->isKernel) { 45 | TimingUtility::start('middleware.out.total'); 46 | } 47 | 48 | // if it was the requestHandler: 49 | self::$middlewareIn?->stopIfNot(); 50 | self::$middlewareIn = null; 51 | 52 | $stopWatch = self::$middlewareOut; 53 | if ($stopWatch) { 54 | $stopWatch->stop(); 55 | $stopWatch->info = $this->info; 56 | } 57 | 58 | if (!$this->isFirst) { 59 | self::$middlewareOut = TimingUtility::stopWatch('middleware.out'); 60 | } 61 | 62 | return $response; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | parallel(); 15 | $rectorConfig->importNames(); 16 | $rectorConfig->importShortClasses(); 17 | $rectorConfig->cacheClass(FileCacheStorage::class); 18 | $rectorConfig->cacheDirectory('./var/cache/rector'); 19 | 20 | $rectorConfig->paths( 21 | array_filter(explode("\n", (string)shell_exec("git ls-files | xargs ls -d 2>/dev/null | grep -E '\.(php)$'"))) 22 | ); 23 | 24 | // define sets of rules 25 | $rectorConfig->sets( 26 | [ 27 | ...RectorSettings::sets(true), 28 | ...RectorSettings::setsTypo3(false), 29 | ] 30 | ); 31 | 32 | // remove some rules 33 | // ignore some files 34 | $rectorConfig->skip( 35 | [ 36 | ...RectorSettings::skip(), 37 | ...RectorSettings::skipTypo3(), 38 | 39 | /** 40 | * rector should not touch these files 41 | */ 42 | RemoveUnusedPublicMethodParameterRector::class, 43 | MakeInheritedMethodVisibilitySameAsParentRector::class => [ 44 | __DIR__ . '/Classes/ServiceProvider.php', 45 | ], 46 | RecastingRemovalRector::class => [ 47 | __DIR__ . '/Classes/SqlLogging/LoggingConnection.php', 48 | ], 49 | ParamTypeByMethodCallTypeRector::class => [ 50 | __DIR__ . '/Classes/SqlLogging/SqlLoggerCore11.php', 51 | ], 52 | //__DIR__ . '/src/Example', 53 | //__DIR__ . '/src/Example.php', 54 | ] 55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kanti/server-timing", 3 | "description": "Show timings of Database and HTTP Calls (send them to Sentry)", 4 | "license": "GPL-2.0-or-later", 5 | "type": "typo3-cms-extension", 6 | "authors": [ 7 | { 8 | "name": "Matthias Vogel", 9 | "email": "git@kanti.de" 10 | } 11 | ], 12 | "require": { 13 | "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", 14 | "composer-runtime-api": "^2.0.0", 15 | "typo3/cms-core": "^11.0 || ^12.0 || ^13.0", 16 | "typo3/cms-extbase": "^11.0 || ^12.0 || ^13.0" 17 | }, 18 | "require-dev": { 19 | "infection/infection": "^0.26.13 || ^0.27.11 || ^0.29.14", 20 | "phpstan/extension-installer": "^1.1", 21 | "phpunit/phpunit": "^10 || ^11", 22 | "pluswerk/grumphp-config": "^7 || ^10", 23 | "saschaegerer/phpstan-typo3": "^1.10.1 || ^2.1.0", 24 | "sentry/sdk": "^3.5", 25 | "ssch/typo3-rector": "^2.6.4 || ^3.3.0", 26 | "typo3/cms-adminpanel": "^11.0 || ^12.0 || ^13.0" 27 | }, 28 | "minimum-stability": "stable", 29 | "autoload": { 30 | "psr-4": { 31 | "Kanti\\ServerTiming\\": "Classes/" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "Kanti\\ServerTiming\\Tests\\": "Tests/" 37 | } 38 | }, 39 | "config": { 40 | "allow-plugins": { 41 | "ergebnis/composer-normalize": true, 42 | "infection/extension-installer": true, 43 | "php-http/discovery": false, 44 | "phpro/grumphp": true, 45 | "phpstan/extension-installer": true, 46 | "pluswerk/grumphp-config": true, 47 | "typo3/class-alias-loader": true, 48 | "typo3/cms-composer-installers": true 49 | } 50 | }, 51 | "extra": { 52 | "typo3/cms": { 53 | "Package": { 54 | "serviceProvider": "Kanti\\ServerTiming\\ServiceProvider" 55 | }, 56 | "extension-key": "server_timing" 57 | } 58 | }, 59 | "scripts": { 60 | "infection": "infection --only-covered", 61 | "test": "@php -d pcov.enabled=1 ./vendor/bin/phpunit --display-warnings", 62 | "test:watch": [ 63 | "Composer\\Config::disableProcessTimeout", 64 | "@php -d pcov.enabled=1 ./vendor/bin/phpunit-watcher watch < /dev/tty" 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Configuration/Services.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | autowire: true 4 | autoconfigure: true 5 | public: true 6 | 7 | Kanti\ServerTiming\: 8 | resource: '../Classes/*' 9 | exclude: 10 | - '../Classes/{Dto,SqlLogging}/*' 11 | - '../Classes/*/WrapMiddleware.php' 12 | - '../Classes/*/XClassMiddlewareDispatcher.php' 13 | 14 | Kanti\ServerTiming\Service\RegisterShutdownFunction\RegisterShutdownFunctionInterface: 15 | class: Kanti\ServerTiming\Service\RegisterShutdownFunction\RegisterShutdownFunction 16 | 17 | Kanti\ServerTiming\Service\SentryServiceInterface: 18 | class: Kanti\ServerTiming\Service\SentryService 19 | 20 | Kanti\ServerTiming\EventListener\ConsoleCommandEventListener: 21 | tags: 22 | - 23 | name: event.listener 24 | identifier: kanti/server-timing/console-command-event-listener 25 | event: Symfony\Component\Console\Event\ConsoleCommandEvent 26 | method: start 27 | - 28 | name: event.listener 29 | identifier: kanti/server-timing/console-terminate-event-listener 30 | event: Symfony\Component\Console\Event\ConsoleTerminateEvent 31 | method: stop 32 | 33 | Kanti\ServerTiming\EventListener\MailEventListener: 34 | tags: 35 | - 36 | name: event.listener 37 | identifier: kanti/server-timing/mail-event-listener 38 | event: TYPO3\CMS\Core\Mail\Event\BeforeMailerSentMessageEvent 39 | method: start 40 | - 41 | name: event.listener 42 | identifier: kanti/server-timing/mail-event-listener 43 | event: TYPO3\CMS\Core\Mail\Event\AfterMailerSentMessageEvent 44 | method: stop 45 | Kanti\ServerTiming\EventListener\BootCompletedEventListener: 46 | tags: 47 | - 48 | name: event.listener 49 | identifier: kanti/server-timing/boot-completed-event-listener 50 | event: TYPO3\CMS\Core\Core\Event\BootCompletedEvent 51 | 52 | Kanti\ServerTiming\EventListener\FileProcessingEventListener: 53 | tags: 54 | - 55 | name: event.listener 56 | identifier: kanti/server-timing/file-processing 57 | event: TYPO3\CMS\Core\Resource\Event\BeforeFileProcessingEvent 58 | method: before 59 | 60 | - 61 | name: event.listener 62 | identifier: kanti/server-timing/file-processing 63 | event: TYPO3\CMS\Core\Resource\Event\AfterFileProcessingEvent 64 | method: after 65 | -------------------------------------------------------------------------------- /Classes/Middleware/XClassMiddlewareDispatcher.php: -------------------------------------------------------------------------------- 1 | tip = new WrapMiddleware($kernel, '', true); 30 | } 31 | 32 | public function handle(ServerRequestInterface $request): ResponseInterface 33 | { 34 | $stop = TimingUtility::stopWatch('bootstrap'); 35 | $stop->startTime = $_SERVER["REQUEST_TIME_FLOAT"]; 36 | $stop->stop(); 37 | 38 | $request = $request->withAttribute('middleware.in.total', TimingUtility::stopWatch('middleware.in.total')); 39 | if ($this->tip instanceof WrapMiddleware) { 40 | $this->tip->isFirst(); 41 | } 42 | 43 | try { 44 | $response = parent::handle($request); 45 | } catch (ImmediateResponseException $immediateResponseException) { 46 | $response = $immediateResponseException->getResponse(); 47 | } 48 | 49 | try { 50 | TimingUtility::end('middleware.out.total'); 51 | } catch (Exception) { 52 | } 53 | 54 | return TimingUtility::getInstance()->shutdown(ScriptResult::fromRequest($request, $response)) ?? $response; 55 | } 56 | 57 | public function add(MiddlewareInterface $middleware): void 58 | { 59 | parent::add($middleware); 60 | 61 | $this->tip = new WrapMiddleware($this->tip, $middleware::class); 62 | } 63 | 64 | public function lazy(string $middleware): void 65 | { 66 | parent::lazy($middleware); 67 | 68 | $this->tip = new WrapMiddleware($this->tip, $middleware); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EXT:server_timing - see your performance 2 | 3 | ![Server-Timing](./Documentation/Server-Timing.png) 4 | 5 | ## installation 6 | 7 | `composer require kanti/server-timing` 8 | 9 | at the moment there is nothing to configure 10 | 11 | > Server timings are not displayed in production for users who are not logged into the backend. 12 | 13 | ## Included measurements: 14 | 15 | - `php`: from start of php call to the register shutdown function 16 | - `console.command`: from start of the console command call 17 | - `middleware.in`: will show how much time was spend in a middleware to prepare the Request 18 | - `middleware.out`: will show how much time was spend in a middleware to change the Response 19 | - `db`: shows the sql query's 20 | - `mail`: shows the mails that are send (only TYPO3 >=12) 21 | - `dataP`: Shows the DataProcessors that were executed 22 | - `extbase`: show all Extbase dispatches, (forwards are included in the original action call) 23 | - `fileProcessing`: show all file processing calls 24 | - `http.client`: external API calls are measured if they use the official TYPO3 `RequestFactory` or the `GuzzleClientFactory`) 25 | 26 | > if a measurement key has more than 4 entries, they will get combined into one total time with a count. 27 | > And the 3 longest entries will be kept 28 | 29 | ## Sentry Tracing 30 | 31 | if you have sentry enabled (different Extension eg. `pluswerk/sentry` or `networkteam/sentry-client`) than you can activate the tracing. 32 | - `sentry_sample_rate` 33 | - if empty ` ` it will keep the setting that was set from the sentry extension. 34 | - if set to a number like `0.1` it will track 10% of all Requests in sentry. 35 | - `sentry_cli_sample_rate` 36 | - just like `sentry_sample_rate` but this setting is for the cli calls of the `typo3` binary 37 | - `stop_watch_limit` is set for long-running processes, if you get memory problems you can lower this setting. (default: 100_000) 38 | - you can force the Tracing of Requests by adding the Cookie `XDEBUG_TRACE` with any value. 39 | ![Tracing](./Documentation/Tracing.png) 40 | 41 | ## Measure your own timings: 42 | 43 | ### `stopWatch` function (recommended) 44 | 45 | ````php 46 | 47 | $stop = \Kanti\ServerTiming\Utility\TimingUtility::stopWatch('doSomething', 'additional Information'); 48 | $result = $this->doSomethingExpensive(); 49 | $stop(); 50 | 51 | ```` 52 | 53 | ### `start` & `stop` functions 54 | 55 | > this has some limitations, there can only be one `doSomething` at a time. 56 | 57 | ````php 58 | 59 | \Kanti\ServerTiming\Utility\TimingUtility::start('doSomething', 'additional Information'); 60 | $result = $this->doSomethingExpensive(); 61 | \Kanti\ServerTiming\Utility\TimingUtility::end('doSomething'); 62 | 63 | ```` 64 | 65 | # TODO List: 66 | 67 | ## todos: 68 | 69 | - more tests 70 | 71 | ## composer patches needed? 72 | 73 | - fluid renderings (possible solution with XClasses?) 74 | 75 | ## wanted: 76 | 77 | - functional tests 78 | 79 | ## nice to have? 80 | 81 | - ViewHelpers 82 | -------------------------------------------------------------------------------- /Classes/Utility/GuzzleUtility.php: -------------------------------------------------------------------------------- 1 | static function (RequestInterface $request, array $options) use ($handler): PromiseInterface { 31 | try { 32 | GeneralUtility::getContainer(); 33 | } catch (LogicException) { 34 | // container not found: 35 | // than we are most likely in a subprocess (spatie/async) 36 | // and we don't want to initialize the container here! 37 | return $handler($request, $options); 38 | } 39 | 40 | $info = $request->getMethod() . ' ' . $request->getUri()->__toString(); 41 | $stop = TimingUtility::getInstance()->stopWatchInternal('http.client', $info); 42 | $request = GeneralUtility::makeInstance(SentryServiceInterface::class)->addSentryTraceHeaders($request, $stop); 43 | 44 | $handlerPromiseCallback = static function ($responseOrException) use ($request, $stop) { 45 | $response = null; 46 | if ($responseOrException instanceof ResponseInterface) { 47 | $response = $responseOrException; 48 | } elseif ($responseOrException instanceof GuzzleRequestException) { 49 | $response = $responseOrException->getResponse(); 50 | } 51 | 52 | $stop->stopIfNot(); 53 | if ($response) { 54 | $stop->info = $request->getMethod() . ' ' . $response->getStatusCode() . ' ' . $request->getUri()->__toString(); 55 | } 56 | 57 | if ($responseOrException instanceof Throwable) { 58 | throw $responseOrException; 59 | } 60 | 61 | return $responseOrException; 62 | }; 63 | try { 64 | return $handler($request, $options)->then($handlerPromiseCallback, $handlerPromiseCallback); 65 | } finally { 66 | $stop->stopIfNot(); 67 | } 68 | }; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Classes/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | 34 | */ 35 | public function getFactories(): array 36 | { 37 | return [ 38 | ApplicationFE::class => self::getApplicationFE(...), 39 | ApplicationBE::class => self::getApplicationBE(...), 40 | ]; 41 | } 42 | 43 | public static function getApplicationFE(ContainerInterface $container): ApplicationFE 44 | { 45 | $requestHandler = new XClassMiddlewareDispatcher( 46 | $container->get(RequestHandlerFE::class), 47 | $container->get('frontend.middlewares'), 48 | $container, 49 | ); 50 | if (version_compare((new Typo3Version())->getBranch(), '13.0', '>=')) { 51 | return new ApplicationFE( 52 | $requestHandler, 53 | $container->get(Context::class), 54 | ); 55 | } 56 | 57 | if (version_compare((new Typo3Version())->getBranch(), '12.0', '>=') && class_exists(BackendEntryPointResolver::class)) { 58 | return new ApplicationFE( 59 | $requestHandler, 60 | $container->get(ConfigurationManager::class), 61 | $container->get(Context::class), 62 | $container->get(BackendEntryPointResolver::class), 63 | ); 64 | } 65 | 66 | return new ApplicationFE( 67 | $requestHandler, 68 | $container->get(ConfigurationManager::class), 69 | $container->get(Context::class), 70 | ); 71 | } 72 | 73 | public static function getApplicationBE(ContainerInterface $container): ApplicationBE 74 | { 75 | $requestHandler = new XClassMiddlewareDispatcher( 76 | $container->get(RequestHandlerBe::class), 77 | $container->get('backend.middlewares'), 78 | $container, 79 | ); 80 | if (version_compare((new Typo3Version())->getBranch(), '13.0', '>=')) { 81 | return new ApplicationBE( 82 | $requestHandler, 83 | $container->get(Context::class), 84 | ); 85 | } 86 | 87 | if (version_compare((new Typo3Version())->getBranch(), '12.0', '>=') && class_exists(BackendEntryPointResolver::class)) { 88 | return new ApplicationBE( 89 | $requestHandler, 90 | $container->get(ConfigurationManager::class), 91 | $container->get(Context::class), 92 | $container->get(BackendEntryPointResolver::class), 93 | ); 94 | } 95 | 96 | return new ApplicationBE( 97 | $requestHandler, 98 | $container->get(ConfigurationManager::class), 99 | $container->get(Context::class), 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /.github/workflows/tasks.yml: -------------------------------------------------------------------------------- 1 | name: Tasks 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | lint-php: 9 | name: "php: ${{ matrix.php }} TYPO3: ${{ matrix.typo3 }} sentry/sdk: ${{ matrix.sentry }}" 10 | runs-on: ubuntu-latest 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | php: [ '8.1', '8.2', '8.3', '8.4' ] 15 | typo3: [ '11', '12', '13' ] 16 | sentry: [ false, true ] 17 | exclude: 18 | - php: '8.1' 19 | typo3: '13' 20 | sentry: true 21 | - php: '8.1' 22 | typo3: '13' 23 | sentry: false 24 | steps: 25 | - name: Setup PHP with PECL extension 26 | uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: ${{ matrix.php }} 29 | # - uses: mirromutth/mysql-action@v1.1 30 | # with: 31 | # mysql version: '5.7' 32 | # mysql database: 'typo3_test' 33 | # mysql root password: 'root' 34 | - uses: actions/checkout@v4 35 | - uses: actions/cache@v4 36 | with: 37 | path: ~/.composer/cache/files 38 | key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} 39 | restore-keys: | 40 | ${{ runner.os }}-${{ matrix.php }}-composer- 41 | - run: composer require typo3/minimal="^${{ matrix.typo3 }}" --dev --ignore-platform-req=php+ 42 | - run: composer remove sentry/sdk --dev --ignore-platform-req=php+ 43 | if: ${{ ! matrix.sentry }} 44 | - run: composer install --no-interaction --no-progress --ignore-platform-req=php+ 45 | - run: ./vendor/bin/grumphp run --ansi 46 | - run: composer test 47 | - run: jq 'del(.logs.html)' infection.json > infection.json.new && mv infection.json.new infection.json 48 | - run: composer infection 49 | - uses: codecov/codecov-action@v5 50 | with: 51 | token: ${{ secrets.CODECOV_TOKEN }} 52 | file: Resources/Public/test-result/clover.xml 53 | 54 | ter-release: 55 | name: TER release 56 | runs-on: ubuntu-latest 57 | if: startsWith(github.ref, 'refs/tags/') 58 | needs: [ lint-php ] 59 | env: 60 | TYPO3_EXTENSION_KEY: 'server_timing' 61 | REPOSITORY_URL: 'https://github.com/Kanti/server-timing' 62 | TYPO3_API_TOKEN: ${{ secrets.TYPO3_API_TOKEN }} 63 | TYPO3_API_USERNAME: ${{ secrets.TYPO3_API_USERNAME }} 64 | TYPO3_API_PASSWORD: ${{ secrets.TYPO3_API_PASSWORD }} 65 | 66 | steps: 67 | - uses: actions/checkout@v4 68 | - name: Get the version 69 | id: get_version 70 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 71 | 72 | - name: Setup PHP 73 | uses: shivammathur/setup-php@v2 74 | with: 75 | php-version: '7.4' 76 | extensions: intl, mbstring, xml, soap, zip, curl 77 | 78 | - name: Install typo3/tailor 79 | run: composer global require typo3/tailor --prefer-dist --no-progress 80 | 81 | - name: Upload EXT:server_timing to TER 82 | run: | 83 | sed -i 's/InstalledVersions::getPrettyVersion('\''kanti\/server-timing'\'')/'\''${{ steps.get_version.outputs.VERSION }}'\''/g' ext_emconf.php \ 84 | && git config --global user.email "no@one" \ 85 | && git config --global user.name "No One" \ 86 | && git add ext_emconf.php \ 87 | && git commit -m 'x' -n \ 88 | && git archive -o archive.zip HEAD --prefix=server_timing-${{ steps.get_version.outputs.VERSION }}/ \ 89 | && git reset --hard HEAD~ \ 90 | && curl -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/Kanti/server-timing/releases/tags/${{ steps.get_version.outputs.VERSION }} > release.json \ 91 | && php ~/.composer/vendor/bin/tailor ter:publish ${{ steps.get_version.outputs.VERSION }} --artefact=archive.zip \ 92 | --comment="$(cat release.json | jq -r '.name') 93 | 94 | $(cat release.json | jq -r '.body') 95 | 96 | $(cat release.json | jq -r '.html_url')" 97 | -------------------------------------------------------------------------------- /Classes/Utility/TimingUtility.php: -------------------------------------------------------------------------------- 1 | */ 53 | private array $stopWatchStack = []; 54 | 55 | /** @return StopWatch[] */ 56 | public function getStopWatches(): array 57 | { 58 | return $this->order; 59 | } 60 | 61 | public static function start(string $key, string $info = ''): void 62 | { 63 | self::getInstance()->startInternal($key, $info); 64 | } 65 | 66 | public function startInternal(string $key, string $info = ''): void 67 | { 68 | if (!$this->shouldTrack()) { 69 | return; 70 | } 71 | 72 | $stop = $this->stopWatchInternal($key, $info); 73 | if (isset($this->stopWatchStack[$key])) { 74 | throw new Exception('only one measurement at a time, use TimingUtility::stopWatch() for parallel measurements', 5736668171); 75 | } 76 | 77 | $this->stopWatchStack[$key] = $stop; 78 | } 79 | 80 | public static function end(string $key): void 81 | { 82 | self::getInstance()->endInternal($key); 83 | } 84 | 85 | public function endInternal(string $key): void 86 | { 87 | if (!$this->shouldTrack()) { 88 | return; 89 | } 90 | 91 | if (!isset($this->stopWatchStack[$key])) { 92 | throw new Exception('where is no measurement with this key', 4685025557); 93 | } 94 | 95 | $stop = $this->stopWatchStack[$key]; 96 | $stop(); 97 | unset($this->stopWatchStack[$key]); 98 | } 99 | 100 | public static function stopWatch(string $key, string $info = ''): StopWatch 101 | { 102 | return self::getInstance()->stopWatchInternal($key, $info); 103 | } 104 | 105 | public function stopWatchInternal(string $key, string $info = ''): StopWatch 106 | { 107 | $stopWatch = new StopWatch($key, $info); 108 | 109 | if ($this->shouldTrack()) { 110 | if (!count($this->order)) { 111 | $phpStopWatch = new StopWatch('php', ''); 112 | $phpStopWatch->startTime = $_SERVER["REQUEST_TIME_FLOAT"]; 113 | $this->order[] = $phpStopWatch; 114 | } 115 | 116 | if (count($this->order) < $this->configService->stopWatchLimit()) { 117 | $this->order[] = $stopWatch; 118 | } 119 | 120 | if (!$this->registered) { 121 | $this->registerShutdownFunction->register(fn(): ?ResponseInterface => $this->shutdown(ScriptResult::fromShutdown())); 122 | $this->registered = true; 123 | } 124 | } 125 | 126 | return $stopWatch; 127 | } 128 | 129 | public function shutdown(ScriptResult $result): ?ResponseInterface 130 | { 131 | if (!$this->shouldTrack()) { 132 | return $result->response; 133 | } 134 | 135 | $this->alreadyShutdown = true; 136 | 137 | 138 | foreach (array_reverse($this->order) as $stopWatch) { 139 | $stopWatch->stopIfNot(); 140 | } 141 | 142 | $container = GeneralUtility::getContainer(); 143 | $response = $container->has(SentryServiceInterface::class) ? $container->get(SentryServiceInterface::class)->sendSentryTrace($result, $this->order) : $result->response; 144 | 145 | if (!$this->shouldAddHeader()) { 146 | return $response; 147 | } 148 | 149 | $timings = []; 150 | $durations = []; 151 | $stopWatches = $this->combineIfToMuch($this->order); 152 | 153 | foreach ($stopWatches as $stopwatch) { 154 | $durations[] = $stopwatch->getDuration(); 155 | } 156 | 157 | rsort($durations); 158 | 159 | $minimumDuration = $durations[$this->configService->getMaxNumberOfTimings() - 1] ?? PHP_INT_MIN; 160 | foreach ($stopWatches as $index => $time) { 161 | $duration = $time->getDuration(); 162 | if ($duration >= $minimumDuration) { 163 | $timings[] = $this->timingString($index, trim($time->key . ' ' . $time->info), $duration); 164 | } 165 | } 166 | 167 | 168 | if (!$timings) { 169 | return $response; 170 | } 171 | 172 | $chunks = $this->chunkStringArray($timings, self::MAX_SINGLE_HEADER_SIZE - strlen('Server-Timing: ')); 173 | 174 | $memoryUsage = $this->humanReadableFileSize(memory_get_peak_usage()); 175 | if ($response) { 176 | return $response 177 | ->withAddedHeader('Server-Timing', $chunks) 178 | ->withAddedHeader('X-Max-Memory-Usage', $memoryUsage); 179 | } 180 | 181 | foreach ($chunks as $chunk) { 182 | header('Server-Timing: ' . $chunk, false); 183 | } 184 | 185 | header('X-Max-Memory-Usage: ' . $memoryUsage, false); 186 | return $response; 187 | } 188 | 189 | private function humanReadableFileSize(int $size): string 190 | { 191 | $fileSizeNames = [" Bytes", " KB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB"]; 192 | $i = floor(log($size, self::BYTE_MULTIPLICATOR)); 193 | return $size ? round($size / (self::BYTE_MULTIPLICATOR ** $i), 2) . $fileSizeNames[$i] : '0 Bytes'; 194 | } 195 | 196 | /** 197 | * @param StopWatch[] $initalStopWatches 198 | * @return StopWatch[] 199 | */ 200 | private function combineIfToMuch(array $initalStopWatches): array 201 | { 202 | $elementsByKey = []; 203 | foreach ($initalStopWatches as $stopWatch) { 204 | if (!isset($elementsByKey[$stopWatch->key])) { 205 | $elementsByKey[$stopWatch->key] = []; 206 | } 207 | 208 | $elementsByKey[$stopWatch->key][] = $stopWatch; 209 | } 210 | 211 | $keepStopWatches = new SplObjectStorage(); 212 | 213 | $insertBefore = []; 214 | foreach ($elementsByKey as $key => $stopWatches) { 215 | $count = count($stopWatches); 216 | if ($count <= 4) { 217 | foreach ($stopWatches as $stopWatch) { 218 | $keepStopWatches->attach($stopWatch); 219 | } 220 | 221 | continue; 222 | } 223 | 224 | $first = $stopWatches[0]; 225 | $sum = array_sum( 226 | array_map( 227 | static fn(StopWatch $stopWatch): float => $stopWatch->getDuration(), 228 | $stopWatches 229 | ) 230 | ); 231 | $insertBefore[$key] = new StopWatch($key, 'count:' . $count); 232 | $insertBefore[$key]->startTime = $first->startTime; 233 | $insertBefore[$key]->stopTime = $insertBefore[$key]->startTime + $sum; 234 | 235 | usort($stopWatches, static fn(StopWatch $a, StopWatch $b): int => $b->getDuration() <=> $a->getDuration()); 236 | 237 | $biggestStopWatches = array_slice($stopWatches, 0, 3); 238 | foreach ($biggestStopWatches as $stopWatch) { 239 | $keepStopWatches->attach($stopWatch); 240 | } 241 | } 242 | 243 | $result = []; 244 | foreach ($initalStopWatches as $stopWatch) { 245 | if (isset($insertBefore[$stopWatch->key])) { 246 | $result[] = $insertBefore[$stopWatch->key]; 247 | unset($insertBefore[$stopWatch->key]); 248 | } 249 | 250 | if (!$keepStopWatches->contains($stopWatch)) { 251 | continue; 252 | } 253 | 254 | $result[] = $stopWatch; 255 | } 256 | 257 | return $result; 258 | } 259 | 260 | private function timingString(int $index, string $description, float $durationInSeconds): string 261 | { 262 | $description = substr($description, 0, $this->configService->getDescriptionLength()); 263 | $description = str_replace(['\\', '"', ';', "\r", "\n"], ["_", "'", ",", "", ""], $description); 264 | return sprintf('%03d;desc="%s";dur=%0.2f', $index, $description, $durationInSeconds * 1000); 265 | } 266 | 267 | private function shouldAddHeader(): bool 268 | { 269 | if (self::$isTesting) { 270 | return true; 271 | } 272 | 273 | if (self::IS_CLI) { 274 | return false; 275 | } 276 | 277 | if ($this->isBackendUser()) { 278 | return true; 279 | } 280 | 281 | return !Environment::getContext()->isProduction(); 282 | } 283 | 284 | public function shouldTrack(): bool 285 | { 286 | return !$this->alreadyShutdown; 287 | } 288 | 289 | private function isBackendUser(): bool 290 | { 291 | return (bool)GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('backend.user', 'isLoggedIn'); 292 | } 293 | 294 | /** 295 | * @param list $timings 296 | * @return list 297 | */ 298 | private function chunkStringArray(array $timings, int $maxLength): array 299 | { 300 | $result = []; 301 | $length = 0; 302 | $index = 0; 303 | foreach ($timings as $timing) { 304 | if ($length <= 0) { 305 | $length = strlen($timing); 306 | } else { 307 | $length += 1 + strlen($timing); 308 | } 309 | 310 | if ($length > $maxLength) { 311 | $index++; 312 | $length = strlen($timing); 313 | } 314 | 315 | $result[$index] ??= ''; 316 | $result[$index] .= ($result[$index] ? ',' : '') . $timing; 317 | } 318 | 319 | return $result; 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /Classes/Service/SentryService.php: -------------------------------------------------------------------------------- 1 | getSentryClient(); 44 | if (!$client) { 45 | return $request; 46 | } 47 | 48 | $parentSpan = SentrySdk::getCurrentHub()->getSpan(); 49 | assert($parentSpan instanceof Span); 50 | $span = $parentSpan->startChild(new SpanContext()); 51 | $stopWatch->span = $span; 52 | return $request 53 | ->withHeader('sentry-trace', $span->toTraceparent()) 54 | ->withHeader('baggage', $span->toBaggage()); 55 | } 56 | 57 | /** 58 | * @param StopWatch[] $stopWatches 59 | */ 60 | public function sendSentryTrace(ScriptResult $result, array $stopWatches): ?ResponseInterface 61 | { 62 | $client = $this->getSentryClient($result->request); 63 | if (!$client) { 64 | return $result->response; 65 | } 66 | 67 | $transaction = $this->startTransaction(); 68 | $transaction->setStartTimestamp($stopWatches[0]->startTime); 69 | $this->addResultToTransaction($result, $transaction); 70 | 71 | $hub = SentrySdk::getCurrentHub(); 72 | 73 | /** @var non-empty-list $stack */ 74 | $stack = [$transaction]; 75 | foreach ($stopWatches as $stopWatch) { 76 | while (count($stack) > 1 && $stopWatch->stopTime > $stack[array_key_last($stack)]->getEndTimestamp()) { 77 | array_pop($stack); 78 | } 79 | 80 | if ($stopWatch->key === 'php') { 81 | continue; 82 | } 83 | 84 | $parent = $stack[array_key_last($stack)]; 85 | if ($stopWatch->span) { 86 | $span = $stopWatch->span; 87 | $span->setParentSpanId($parent->getSpanId()); 88 | } else { 89 | $span = $parent->startChild(new SpanContext()); 90 | } 91 | 92 | $this->updateSpan($span, $stopWatch); 93 | $hub->setSpan($span); 94 | $stack[] = $span; 95 | } 96 | 97 | $hub->setSpan($transaction); 98 | 99 | $options = $client->getOptions(); 100 | $should = $options->shouldAttachStacktrace(); 101 | $options->setAttachStacktrace(false); 102 | $transaction->finish($stopWatches[0]->stopTime); 103 | $options->setAttachStacktrace($should); 104 | 105 | return $this->addSentryTraceToResponse($result->response); 106 | } 107 | 108 | private function getSentryClient(?ServerRequestInterface $serverRequest = null): ?ClientInterface 109 | { 110 | if (!class_exists(SentrySdk::class)) { 111 | return null; 112 | } 113 | 114 | $hub = SentrySdk::getCurrentHub(); 115 | $client = $hub->getClient(); 116 | if (!$client && class_exists(PluswerkSentry::class)) { 117 | $client = PluswerkSentry::getInstance()->getClient(); 118 | } 119 | 120 | if (!$client) { 121 | return null; 122 | } 123 | 124 | $options = $client->getOptions(); 125 | 126 | $forceTrace = $this->isTraceForced($serverRequest); 127 | 128 | $options->setTracesSampleRate((float)($forceTrace ?: $this->configService->tracesSampleRate() ?? $options->getTracesSampleRate())); 129 | $options->setEnableTracing($forceTrace ?: $this->configService->enableTracing() ?? $options->getEnableTracing()); 130 | 131 | $this->startTransaction(); 132 | 133 | return $client; 134 | } 135 | 136 | private function addSentryTraceToResponse(?ResponseInterface $response): ?ResponseInterface 137 | { 138 | if (!$response) { 139 | return null; 140 | } 141 | 142 | if (!str_starts_with($response->getHeaderLine('Content-Type'), 'text/html')) { 143 | return $response; 144 | } 145 | 146 | $stream = $response->getBody(); 147 | $stream->rewind(); 148 | 149 | $html = $stream->getContents(); 150 | $position = stripos($html, '') ?: stripos($html, '', getTraceparent()); 156 | $sentryMetaTags .= sprintf('', getBaggage()); 157 | 158 | $html = substr($html, 0, $position) . $sentryMetaTags . substr($html, $position); 159 | 160 | return new HtmlResponse($html, $response->getStatusCode(), [...$response->getHeaders(), 'Content-Length' => strlen($html)]); 161 | } 162 | 163 | private function createTransactionContextFromCli(): TransactionContext 164 | { 165 | $transactionContext = new TransactionContext(); 166 | $transactionContext->setName(implode(' ', $_SERVER['argv'])); 167 | $transactionContext->setOp('typo3.cli'); 168 | 169 | return $transactionContext; 170 | } 171 | 172 | private function createTransactionContextFromRequest(ServerRequestInterface $serverRequest): TransactionContext 173 | { 174 | $sentryTrace = $serverRequest->getHeaderLine('sentry-trace'); 175 | $baggage = $serverRequest->getHeaderLine('baggage'); 176 | 177 | if ($sentryTrace) { 178 | $transactionContext = continueTrace($sentryTrace, $baggage); 179 | } else { 180 | $transactionContext = new TransactionContext(); 181 | } 182 | 183 | $transactionContext->setName($serverRequest->getMethod() . ' ' . $serverRequest->getUri()); 184 | $transactionContext->setOp('http.server'); 185 | $transactionContext->setData([ 186 | 'client.address' => GeneralUtility::getIndpEnv('REMOTE_ADDR'), 187 | 'url.scheme' => $serverRequest->getUri()->getScheme(), 188 | 'server.address' => $serverRequest->getUri()->getHost(), 189 | 'server.port' => $serverRequest->getUri()->getPort(), 190 | 'url.path' => $serverRequest->getUri()->getPath(), 191 | 'url.query' => $serverRequest->getQueryParams(), 192 | 'url.full' => (string)$serverRequest->getUri(), 193 | 'request.method' => $serverRequest->getMethod(), 194 | 'request.query' => $serverRequest->getQueryParams(), 195 | 'request.body' => $serverRequest->getParsedBody(), 196 | 'request.headers' => $serverRequest->getHeaders(), 197 | 'request.cookies' => $serverRequest->getCookieParams(), 198 | 'request.url' => (string)$serverRequest->getUri(), 199 | // TODO add body sizes 200 | ]); 201 | 202 | return $transactionContext; 203 | } 204 | 205 | private function addResultToTransaction(ScriptResult $result, Transaction $transaction): Transaction 206 | { 207 | if ($result->isCli()) { 208 | if ($result->cliExitCode !== null) { 209 | $transaction->setStatus($result->cliExitCode ? SpanStatus::unknownError() : SpanStatus::ok()); 210 | } 211 | 212 | return $transaction; 213 | } 214 | 215 | $statusCode = $result->response?->getStatusCode() ?? http_response_code(); 216 | if (is_int($statusCode)) { 217 | $transaction->setStatus(SpanStatus::createFromHttpStatusCode($statusCode)); 218 | } 219 | 220 | return $transaction; 221 | } 222 | 223 | 224 | private function isTraceForced(?ServerRequestInterface $serverRequest): bool 225 | { 226 | $serverRequest = $this->getServerRequest($serverRequest); 227 | 228 | if (!$serverRequest) { 229 | return false; 230 | } 231 | 232 | return isset($serverRequest->getCookieParams()['XDEBUG_TRACE']); 233 | } 234 | 235 | private function startTransaction(): Transaction 236 | { 237 | if ($this->transaction) { 238 | return $this->transaction; 239 | } 240 | 241 | if (TimingUtility::IS_CLI) { 242 | $transactionContext = $this->createTransactionContextFromCli(); 243 | } else { 244 | $serverRequest = $this->getServerRequest(); 245 | assert($serverRequest instanceof ServerRequestInterface); 246 | $transactionContext = $this->createTransactionContextFromRequest($serverRequest); 247 | } 248 | 249 | 250 | $hub = SentrySdk::getCurrentHub(); 251 | $this->transaction = $hub->startTransaction($transactionContext); 252 | $hub->setSpan($this->transaction); 253 | return $this->transaction; 254 | } 255 | 256 | private function updateSpan(Span $span, StopWatch $stopWatch): void 257 | { 258 | $span->setOp($stopWatch->key); 259 | $span->setStartTimestamp($stopWatch->startTime); 260 | 261 | $description = $stopWatch->info; 262 | if ($stopWatch->key === 'http.client') { 263 | $info = explode(' ', $stopWatch->info, 3); 264 | $uri = new Uri($info[1] ?? ''); 265 | $partialUri = Uri::fromParts([ 266 | 'scheme' => $uri->getScheme(), 267 | 'host' => $uri->getHost(), 268 | 'port' => $uri->getPort(), 269 | 'path' => $uri->getPath(), 270 | ]); 271 | $description = $info[0] . ' ' . $partialUri . ' ' . ($info[2] ?? ''); 272 | $span->setData([ 273 | 'http.query' => $uri->getQuery(), 274 | 'http.fragment' => $uri->getFragment(), 275 | ]); 276 | } 277 | 278 | $span->setDescription($description); 279 | $span->finish($stopWatch->stopTime); 280 | } 281 | 282 | private function getServerRequest(?ServerRequestInterface $serverRequest = null): ?ServerRequestInterface 283 | { 284 | $serverRequest ??= $GLOBALS['TYPO3_REQUEST'] ?? null; 285 | $serverRequest ??= TimingUtility::IS_CLI ? null : ServerRequestFactory::fromGlobals(); 286 | return $serverRequest; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /Tests/TimingUtilityTest.php: -------------------------------------------------------------------------------- 1 | getTestInstance()); 39 | } 40 | 41 | protected function tearDown(): void 42 | { 43 | unset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['server_timing']); 44 | GeneralUtility::resetSingletonInstances([]); 45 | } 46 | 47 | #[Test] 48 | public function getInstance(): void 49 | { 50 | self::assertInstanceOf(TimingUtility::class, $firstCall = TimingUtility::getInstance()); 51 | // even if we clear GeneralUtility, the instance should be the same 52 | // we measure so erly in TYPO3s bootstrap, that GeneralUtility singletons are cleared after our first measurement 53 | // but we need to be sure, that the instance is not recreated as that will run the shutdown function again, and that is not good! 54 | GeneralUtility::resetSingletonInstances([]); 55 | self::assertSame($firstCall, TimingUtility::getInstance()); 56 | } 57 | 58 | #[Test] 59 | public function stopWatch(): void 60 | { 61 | $stopWatch = TimingUtility::stopWatch('testStopWatch'); 62 | self::assertNull($stopWatch->stopTime); 63 | $stopWatch(); 64 | $stopTime = $stopWatch->stopTime; 65 | self::assertIsFloat($stopTime); 66 | self::assertIsFloat($stopWatch->getDuration()); 67 | self::assertSame($stopTime, $stopWatch->stopTime); 68 | self::assertGreaterThan(0, $stopWatch->getDuration()); 69 | self::assertLessThan(1, $stopWatch->getDuration()); 70 | } 71 | 72 | #[Test] 73 | public function stopWatchStop(): void 74 | { 75 | $stopWatch = TimingUtility::stopWatch('testStopWatch'); 76 | self::assertNull($stopWatch->stopTime); 77 | $stopWatch->stop(); 78 | $stopTime = $stopWatch->stopTime; 79 | self::assertIsFloat($stopTime); 80 | self::assertIsFloat($stopWatch->getDuration()); 81 | self::assertSame($stopTime, $stopWatch->stopTime); 82 | self::assertGreaterThan(0.0, $stopWatch->getDuration()); 83 | self::assertLessThan(1.0, $stopWatch->getDuration()); 84 | } 85 | 86 | #[Test] 87 | public function stopWatchStopIfNot(): void 88 | { 89 | $stopWatch = TimingUtility::stopWatch('testStopWatch'); 90 | self::assertNull($stopWatch->stopTime); 91 | $stopWatch->stopIfNot(); 92 | self::assertGreaterThan(0.0, $stopWatch->stopTime); 93 | $stopWatch->stopTime = 0.0; 94 | $stopWatch->stopIfNot(); 95 | self::assertSame(0.0, $stopWatch->stopTime); 96 | } 97 | 98 | #[Test] 99 | public function stopWatchInternal(): void 100 | { 101 | $stopWatch = TimingUtility::getInstance()->stopWatchInternal('test'); 102 | self::assertInstanceOf(StopWatch::class, $stopWatch); 103 | } 104 | 105 | #[Test] 106 | public function stopWatchFirstIsAlwaysPhp(): void 107 | { 108 | $timingUtility = $this->getTestInstance(); 109 | $timingUtility->stopWatchInternal('test'); 110 | 111 | $watches = $timingUtility->getStopWatches(); 112 | self::assertCount(2, $watches); 113 | self::assertSame('php', $watches[0]->key); 114 | self::assertSame('test', $watches[1]->key); 115 | } 116 | 117 | #[Test] 118 | public function stopWatchLimit(): void 119 | { 120 | $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['server_timing']['stop_watch_limit'] = 3; 121 | $timingUtility = $this->getTestInstance(); 122 | $timingUtility->stopWatchInternal('test'); 123 | $timingUtility->stopWatchInternal('test'); 124 | $timingUtility->stopWatchInternal('test'); 125 | 126 | $watches = $timingUtility->getStopWatches(); 127 | self::assertCount(3, $watches); 128 | self::assertSame('php', $watches[0]->key); 129 | self::assertSame('test', $watches[1]->key); 130 | self::assertSame('test', $watches[2]->key); 131 | } 132 | 133 | #[Test] 134 | public function didRegisterShutdownFunctionOnce(): void 135 | { 136 | $timingUtility = new TimingUtility($registerShutdownFunction = new RegisterShutdownFunctionNoop(), new ConfigService()); 137 | $timingUtility->stopWatchInternal('test'); 138 | $timingUtility->stopWatchInternal('test'); 139 | $timingUtility->stopWatchInternal('test'); 140 | $timingUtility->stopWatchInternal('test'); 141 | self::assertSame(1, $registerShutdownFunction->callCount); 142 | } 143 | 144 | #[Test] 145 | public function stopWatchGetDuration(): void 146 | { 147 | $stopWatch = TimingUtility::stopWatch('testStopWatch'); 148 | self::assertNull($stopWatch->stopTime); 149 | self::assertIsFloat($stopWatch->getDuration()); 150 | self::assertIsFloat($stopWatch->stopTime); 151 | self::assertGreaterThan(0, $stopWatch->getDuration()); 152 | self::assertLessThan(1, $stopWatch->getDuration()); 153 | } 154 | 155 | /** 156 | * @param array{0:int, 1:string, 2:float} $args 157 | */ 158 | #[Test] 159 | #[DataProvider('dataProviderTimingString')] 160 | public function timingString(string $expected, array $args): void 161 | { 162 | $reflection = new ReflectionClass(TimingUtility::class); 163 | $reflectionMethod = $reflection->getMethod('timingString'); 164 | 165 | $result = $reflectionMethod->invoke($this->getTestInstance(), ...$args); 166 | self::assertSame($expected, $result); 167 | } 168 | 169 | public static function dataProviderTimingString(): Generator 170 | { 171 | yield 'simple' => [ 172 | '000;desc="key";dur=12210.00', 173 | [0, 'key', 12.21], 174 | ]; 175 | yield 'toLong' => [ 176 | '000;desc="this key veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVer";dur=12210.00', 177 | [ 178 | 0, 179 | 'this key veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLong', 180 | 12.21, 181 | ], 182 | ]; 183 | yield 'specialChar' => [ 184 | '000;desc=",_\'";dur=12219.88', 185 | [0, ';\\"', 12.21987654], 186 | ]; 187 | } 188 | 189 | #[Test] 190 | #[DataProvider('dataProviderHumanReadableFileSize')] 191 | public function testHumanReadableFileSize(string $expected, int $size): void 192 | { 193 | $reflection = new ReflectionClass(TimingUtility::class); 194 | $reflectionMethod = $reflection->getMethod('humanReadableFileSize'); 195 | 196 | $result = $reflectionMethod->invoke($this->getTestInstance(), $size); 197 | self::assertSame($expected, $result); 198 | } 199 | 200 | public static function dataProviderHumanReadableFileSize(): Generator 201 | { 202 | yield 'simple' => [ 203 | '48.89 MB', 204 | 51268468, 205 | ]; 206 | yield 'big' => [ 207 | '1 EB', 208 | 1024 ** 6, 209 | ]; 210 | } 211 | 212 | /** 213 | * @param StopWatch[] $stopWatches 214 | */ 215 | #[Test] 216 | #[DataProvider('dataProviderShutdown')] 217 | public function shutdown(string $expected, array $stopWatches, ?int $numberOfTimings, ?int $lengthOfDescription): void 218 | { 219 | $timingUtility = $this->getTestInstance($stopWatches); 220 | 221 | $container = new Container(); 222 | $container->set(SentryService::class, null); 223 | GeneralUtility::setContainer($container); 224 | 225 | if ($numberOfTimings) { 226 | $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['server_timing']['number_of_timings'] = $numberOfTimings; 227 | } 228 | 229 | if ($lengthOfDescription) { 230 | $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['server_timing']['length_of_description'] = $lengthOfDescription; 231 | } 232 | 233 | $reflection = new ReflectionClass(TimingUtility::class); 234 | $isAlreadyShutdown = $reflection->getProperty('alreadyShutdown'); 235 | self::assertFalse($isAlreadyShutdown->getValue($timingUtility)); 236 | 237 | $response = $timingUtility->shutdown(ScriptResult::fromRequest(new ServerRequest(), new Response())); 238 | $result = $response?->getHeader('Server-Timing')[0]; 239 | self::assertSame($expected, $result); 240 | 241 | self::assertTrue($isAlreadyShutdown->getValue($timingUtility)); 242 | } 243 | 244 | public static function dataProviderShutdown(): Generator 245 | { 246 | $stopWatch1 = new StopWatch('key1', 'info'); 247 | $stopWatch1->startTime = 100003.0003; 248 | $stopWatch1->stopTime = 100004.0003; 249 | 250 | $stopWatch2 = new StopWatch('key2', 'info for longer description'); 251 | $stopWatch2->startTime = 100004.0004; 252 | $stopWatch2->stopTime = 100015.0004; 253 | 254 | $stopWatch3 = new StopWatch(' key3', 'info'); 255 | $stopWatch3->startTime = 100004.0004; 256 | $stopWatch3->stopTime = 100025.0004; 257 | 258 | $stopWatch4 = new StopWatch('key4', 'short duration'); 259 | $stopWatch4->startTime = 100003.0003; 260 | $stopWatch4->stopTime = 100004.0000; 261 | 262 | yield 'simple' => [ 263 | '000;desc="key1 info";dur=1000.00,001;desc="key2 info for longer description";dur=11000.00,002;desc="key3 info";dur=21000.00,003;desc="key4 short duration";dur=999.70', 264 | 'stopWatches' => [$stopWatch1, $stopWatch2, $stopWatch3, $stopWatch4], 265 | 'numberOfTimings' => null, 266 | 'lengthOfDescription' => null, 267 | ]; 268 | yield 'numberOfTimings' => [ 269 | '001;desc="key2 info for longer description";dur=11000.00,002;desc="key3 info";dur=21000.00', 270 | 'stopWatches' => [$stopWatch1, $stopWatch2, $stopWatch3], 271 | 'numberOfTimings' => 2, 272 | 'lengthOfDescription' => null, 273 | ]; 274 | yield 'lengthOfDescription' => [ 275 | '000;desc="key1 info";dur=1000.00,001;desc="key2 info ";dur=11000.00,002;desc="key3 info";dur=21000.00', 276 | 'stopWatches' => [$stopWatch1, $stopWatch2, $stopWatch3], 277 | 'numberOfTimings' => null, 278 | 'lengthOfDescription' => 10, 279 | ]; 280 | yield 'unsorted_stop_watches' => [ 281 | '000;desc="key2 info for longer description";dur=11000.00,002;desc="key3 info";dur=21000.00', 282 | 'stopWatches' => [$stopWatch2, $stopWatch1, $stopWatch3], 283 | 'numberOfTimings' => 2, 284 | 'lengthOfDescription' => null, 285 | ]; 286 | yield 'less_timings' => [ 287 | '000;desc="key1 info";dur=1000.00', 288 | 'stopWatches' => [$stopWatch1], 289 | 'numberOfTimings' => 5, 290 | 'lengthOfDescription' => null, 291 | ]; 292 | } 293 | 294 | #[Test] 295 | public function sendSentryTrace(): void 296 | { 297 | $providedStopWatches = []; 298 | for ($i = 1; $i < 100; $i++) { 299 | $stopWatch = new StopWatch('key-' . $i, 'info'); 300 | $stopWatch->startTime = 100001.0000 + $i; 301 | $providedStopWatches[] = $stopWatch; 302 | } 303 | 304 | $timingUtility = $this->getTestInstance($providedStopWatches); 305 | 306 | $container = new Container(); 307 | $container->set( 308 | SentryServiceInterface::class, 309 | new class implements SentryServiceInterface { 310 | public function addSentryTraceHeaders(RequestInterface $request, StopWatch $stopWatch): RequestInterface 311 | { 312 | return $request; 313 | } 314 | 315 | /** 316 | * @param list $stopWatches 317 | */ 318 | public function sendSentryTrace(ScriptResult $result, array $stopWatches): ?ResponseInterface 319 | { 320 | $sortedWatches = $stopWatches; 321 | usort($sortedWatches, static fn($a, $b): int => $b->stopTime <=> $a->stopTime); 322 | TimingUtilityTest::assertSame($sortedWatches, $stopWatches); 323 | TimingUtilityTest::assertNotNull($stopWatches[0]->stopTime); 324 | return new HtmlResponse(''); 325 | } 326 | } 327 | ); 328 | GeneralUtility::setContainer($container); 329 | 330 | $timingUtility->shutdown(ScriptResult::fromRequest(new ServerRequest(), new Response())); 331 | } 332 | 333 | /** 334 | * @param StopWatch[] $expected 335 | * @param StopWatch[] $initalStopWatches 336 | */ 337 | #[Test] 338 | #[DataProvider('dataProviderCombineIfToMuch')] 339 | public function combineIfToMuch(array $expected, array $initalStopWatches): void 340 | { 341 | $reflection = new ReflectionClass(TimingUtility::class); 342 | $initalStopWatches = array_map(static fn(StopWatch $el): StopWatch => clone $el, $initalStopWatches); 343 | $reflectionMethod = $reflection->getMethod('combineIfToMuch'); 344 | 345 | $result = $reflectionMethod->invoke($this->getTestInstance(), $initalStopWatches); 346 | self::assertEqualsWithDelta($expected, $result, 0.00001); 347 | } 348 | 349 | public static function dataProviderCombineIfToMuch(): Generator 350 | { 351 | $stopWatchX = new StopWatch('x', 'info'); 352 | $stopWatchX->startTime = 100001.0001; 353 | $stopWatchX->stopTime = 100002.0001; 354 | 355 | $stopWatchX2 = new StopWatch('x', 'info'); 356 | $stopWatchX2->startTime = 100002.0002; 357 | $stopWatchX2->stopTime = 100003.0002; 358 | 359 | $stopWatchA = new StopWatch('key', 'info'); 360 | $stopWatchA->startTime = 100003.0003; 361 | $stopWatchA->stopTime = 100004.0003; 362 | 363 | $stopWatchB = new StopWatch('key', 'info'); 364 | $stopWatchB->startTime = 100004.0004; 365 | $stopWatchB->stopTime = 100005.0004; 366 | 367 | $stopWatchBig1 = new StopWatch('key', 'info'); 368 | $stopWatchBig1->startTime = 100005.0005; 369 | $stopWatchBig1->stopTime = 100106.0005; 370 | 371 | $stopWatchD = new StopWatch('key', 'info'); 372 | $stopWatchD->startTime = 100006.0006; 373 | $stopWatchD->stopTime = 100007.0006; 374 | 375 | $stopWatchBig2 = new StopWatch('key', 'info'); 376 | $stopWatchBig2->startTime = 100007.0007; 377 | $stopWatchBig2->stopTime = 100108.0007; 378 | 379 | yield 'empty' => [ 380 | [], 381 | [], 382 | ]; 383 | yield 'simple' => [ 384 | [$stopWatchA], 385 | [$stopWatchA], 386 | ]; 387 | yield 'keepsOrder' => [ 388 | [$stopWatchA, $stopWatchB], 389 | [$stopWatchA, $stopWatchB], 390 | ]; 391 | yield 'nearlyCombine' => [ 392 | [$stopWatchA, $stopWatchB, $stopWatchBig1, $stopWatchD], 393 | [$stopWatchA, $stopWatchB, $stopWatchBig1, $stopWatchD], 394 | ]; 395 | $combined = new StopWatch('key', 'count:5'); 396 | $combined->startTime = $stopWatchA->startTime; 397 | $combined->stopTime = $stopWatchA->startTime 398 | + $stopWatchA->getDuration() 399 | + $stopWatchB->getDuration() 400 | + $stopWatchBig1->getDuration() 401 | + $stopWatchD->getDuration() 402 | + $stopWatchBig2->getDuration(); 403 | yield 'combine:simple' => [ 404 | [$combined, $stopWatchA, $stopWatchBig1, $stopWatchBig2], 405 | [$stopWatchA, $stopWatchB, $stopWatchBig1, $stopWatchD, $stopWatchBig2], 406 | ]; 407 | $combined = new StopWatch('key', 'count:5'); 408 | $combined->startTime = $stopWatchB->startTime; 409 | $combined->stopTime = $stopWatchB->startTime 410 | + $stopWatchA->getDuration() 411 | + $stopWatchB->getDuration() 412 | + $stopWatchBig1->getDuration() 413 | + $stopWatchD->getDuration() 414 | + $stopWatchBig2->getDuration(); 415 | yield 'combine:keepOrder' => [ 416 | [$stopWatchX, $combined, $stopWatchB, $stopWatchBig1, $stopWatchX2, $stopWatchBig2], 417 | [$stopWatchX, $stopWatchB, $stopWatchBig1, $stopWatchX2, $stopWatchD, $stopWatchBig2, $stopWatchA], 418 | ]; 419 | } 420 | 421 | #[Test] 422 | public function shouldTrack(): void 423 | { 424 | $reflection = new ReflectionClass(TimingUtility::class); 425 | $isAlreadyShutdown = $reflection->getProperty('alreadyShutdown'); 426 | 427 | $timingUtility = $this->getTestInstance(); 428 | 429 | $isAlreadyShutdown->setValue($timingUtility, false); 430 | self::assertTrue($timingUtility->shouldTrack()); 431 | 432 | $isAlreadyShutdown->setValue($timingUtility, true); 433 | self::assertFalse($timingUtility->shouldTrack()); 434 | } 435 | 436 | /** 437 | * @param list $timings 438 | * @param list $expected 439 | */ 440 | #[Test] 441 | #[DataProvider('chunkStringArrayDataProvider')] 442 | public function chunkStringArray(array $timings, int $maxLength, array $expected): void 443 | { 444 | $reflection = new ReflectionClass(TimingUtility::class); 445 | $reflectionMethod = $reflection->getMethod('chunkStringArray'); 446 | $reflectionMethod->setAccessible(true); 447 | 448 | $result = $reflectionMethod->invoke($this->getTestInstance(), $timings, $maxLength); 449 | self::assertSame($expected, $result); 450 | self::assertIsList($result); 451 | } 452 | 453 | /** 454 | * @return Generator, maxLength: int, expected: list}> 455 | */ 456 | public static function chunkStringArrayDataProvider(): Generator 457 | { 458 | yield 'maxLength 1' => [ 459 | 'timings' => ['a', 'b', 'c', 'd', 'e', 'f'], 460 | 'maxLength' => 1, 461 | 'expected' => ['a', 'b', 'c', 'd', 'e', 'f'], 462 | ]; 463 | yield 'maxLength 2' => [ 464 | 'timings' => ['a', 'b', 'c', 'd', 'e', 'f'], 465 | 'maxLength' => 2, 466 | 'expected' => ['a', 'b', 'c', 'd', 'e', 'f'], 467 | ]; 468 | yield 'maxLength 3' => [ 469 | 'timings' => ['a', 'b', 'c', 'd', 'e', 'f'], 470 | 'maxLength' => 3, 471 | 'expected' => ['a,b', 'c,d', 'e,f'], 472 | ]; 473 | yield 'maxLength 4' => [ 474 | 'timings' => ['a', 'b', 'c', 'd', 'e', 'f'], 475 | 'maxLength' => 4, 476 | 'expected' => ['a,b', 'c,d', 'e,f'], 477 | ]; 478 | yield 'maxLength 5' => [ 479 | 'timings' => ['a', 'b', 'c', 'd', 'e', 'f'], 480 | 'maxLength' => 5, 481 | 'expected' => ['a,b,c', 'd,e,f'], 482 | ]; 483 | yield 'maxLength 6' => [ 484 | 'timings' => ['a', 'b', 'c', 'd', 'e', 'f'], 485 | 'maxLength' => 6, 486 | 'expected' => ['a,b,c', 'd,e,f'], 487 | ]; 488 | yield 'maxLength 7' => [ 489 | 'timings' => ['a', 'b', 'c', 'd', 'e', 'f'], 490 | 'maxLength' => 7, 491 | 'expected' => ['a,b,c,d', 'e,f'], 492 | ]; 493 | yield 'maxLength 8' => [ 494 | 'timings' => ['a', 'b', 'c', 'd', 'e', 'f'], 495 | 'maxLength' => 8, 496 | 'expected' => ['a,b,c,d', 'e,f'], 497 | ]; 498 | yield 'maxLength 9' => [ 499 | 'timings' => ['a', 'b', 'c', 'd', 'e', 'f'], 500 | 'maxLength' => 9, 501 | 'expected' => ['a,b,c,d,e', 'f'], 502 | ]; 503 | yield 'maxLength 10' => [ 504 | 'timings' => ['a', 'b', 'c', 'd', 'e', 'f'], 505 | 'maxLength' => 10, 506 | 'expected' => ['a,b,c,d,e', 'f'], 507 | ]; 508 | yield 'maxLength 11' => [ 509 | 'timings' => ['a', 'b', 'c', 'd', 'e', 'f'], 510 | 'maxLength' => 11, 511 | 'expected' => ['a,b,c,d,e,f'], 512 | ]; 513 | } 514 | 515 | /** 516 | * @param StopWatch[] $stopWatches 517 | */ 518 | private function getTestInstance(array $stopWatches = []): TimingUtility 519 | { 520 | $timingUtility = new TimingUtility(new RegisterShutdownFunctionNoop(), new ConfigService()); 521 | $timingUtility::$isTesting = true; 522 | $reflection = new ReflectionClass($timingUtility); 523 | $reflection->getProperty('order')->setValue($timingUtility, $stopWatches); 524 | return $timingUtility; 525 | } 526 | } 527 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - 4 | message: "#^Parameter \\#1 \\$loggers of class Doctrine\\\\DBAL\\\\Logging\\\\LoggerChain constructor expects iterable\\, array\\ given\\.$#" 5 | count: 1 6 | path: Classes/Middleware/AdminpanelSqlLoggingMiddleware.php 7 | 8 | - 9 | message: "#^Using nullsafe method call on non\\-nullable type Kanti\\\\ServerTiming\\\\Dto\\\\StopWatch\\. Use \\-\\> instead\\.$#" 10 | count: 1 11 | path: Classes/Middleware/WrapMiddleware.php 12 | 13 | - 14 | message: "#^Class TYPO3\\\\CMS\\\\Backend\\\\Http\\\\Application constructor invoked with 2 parameters, 4 required\\.$#" 15 | count: 1 16 | path: Classes/ServiceProvider.php 17 | 18 | - 19 | message: "#^Class TYPO3\\\\CMS\\\\Backend\\\\Http\\\\Application constructor invoked with 3 parameters, 4 required\\.$#" 20 | count: 1 21 | path: Classes/ServiceProvider.php 22 | 23 | - 24 | message: "#^Class TYPO3\\\\CMS\\\\Frontend\\\\Http\\\\Application constructor invoked with 2 parameters, 4 required\\.$#" 25 | count: 1 26 | path: Classes/ServiceProvider.php 27 | 28 | - 29 | message: "#^Class TYPO3\\\\CMS\\\\Frontend\\\\Http\\\\Application constructor invoked with 3 parameters, 4 required\\.$#" 30 | count: 1 31 | path: Classes/ServiceProvider.php 32 | 33 | - 34 | message: "#^Parameter \\#2 \\$configurationManager of class TYPO3\\\\CMS\\\\Backend\\\\Http\\\\Application constructor expects TYPO3\\\\CMS\\\\Core\\\\Configuration\\\\ConfigurationManager, TYPO3\\\\CMS\\\\Core\\\\Context\\\\Context given\\.$#" 35 | count: 1 36 | path: Classes/ServiceProvider.php 37 | 38 | - 39 | message: "#^Parameter \\#2 \\$configurationManager of class TYPO3\\\\CMS\\\\Frontend\\\\Http\\\\Application constructor expects TYPO3\\\\CMS\\\\Core\\\\Configuration\\\\ConfigurationManager, TYPO3\\\\CMS\\\\Core\\\\Context\\\\Context given\\.$#" 40 | count: 1 41 | path: Classes/ServiceProvider.php 42 | 43 | - 44 | message: "#^Parameter \\#1 \\$loggers of class Doctrine\\\\DBAL\\\\Logging\\\\LoggerChain constructor expects iterable\\, array\\ given\\.$#" 45 | count: 1 46 | path: Classes/Middleware/AdminpanelSqlLoggingMiddleware.php 47 | 48 | - 49 | message: "#^Call to method getMessage\\(\\) on an unknown class TYPO3\\\\CMS\\\\Core\\\\Mail\\\\Event\\\\BeforeMailerSentMessageEvent\\.$#" 50 | count: 1 51 | path: Classes/EventListener/MailEventListener.php 52 | 53 | - 54 | message: "#^Parameter \\$event of method Kanti\\\\ServerTiming\\\\EventListener\\\\MailEventListener\\:\\:start\\(\\) has invalid type TYPO3\\\\CMS\\\\Core\\\\Mail\\\\Event\\\\BeforeMailerSentMessageEvent\\.$#" 55 | count: 1 56 | path: Classes/EventListener/MailEventListener.php 57 | 58 | - 59 | message: "#^Parameter \\$event of method Kanti\\\\ServerTiming\\\\EventListener\\\\MailEventListener\\:\\:stop\\(\\) has invalid type TYPO3\\\\CMS\\\\Core\\\\Mail\\\\Event\\\\AfterMailerSentMessageEvent\\.$#" 60 | count: 1 61 | path: Classes/EventListener/MailEventListener.php 62 | 63 | - 64 | message: "#^Using nullsafe method call on non\\-nullable type Kanti\\\\ServerTiming\\\\Dto\\\\StopWatch\\. Use \\-\\> instead\\.$#" 65 | count: 1 66 | path: Classes/Middleware/WrapMiddleware.php 67 | 68 | - 69 | message: "#^Class TYPO3\\\\CMS\\\\Backend\\\\Http\\\\Application constructor invoked with 4 parameters, 3 required\\.$#" 70 | count: 1 71 | path: Classes/ServiceProvider.php 72 | 73 | - 74 | message: "#^Class TYPO3\\\\CMS\\\\Frontend\\\\Http\\\\Application constructor invoked with 4 parameters, 3 required\\.$#" 75 | count: 1 76 | path: Classes/ServiceProvider.php 77 | 78 | - 79 | message: "#^Class TYPO3\\\\CMS\\\\Backend\\\\Http\\\\Application constructor invoked with 3 parameters, 4 required\\.$#" 80 | count: 1 81 | path: Classes/ServiceProvider.php 82 | 83 | - 84 | message: "#^Class TYPO3\\\\CMS\\\\Frontend\\\\Http\\\\Application constructor invoked with 3 parameters, 4 required\\.$#" 85 | count: 1 86 | path: Classes/ServiceProvider.php 87 | 88 | - 89 | message: "#^Property Kanti\\\\ServerTiming\\\\Dto\\\\StopWatch\\:\\:\\$span has unknown class Sentry\\\\Tracing\\\\Span as its type\\.$#" 90 | count: 1 91 | path: Classes/Dto/StopWatch.php 92 | 93 | - 94 | message: "#^Call to method finish\\(\\) on an unknown class Sentry\\\\Tracing\\\\Span\\.$#" 95 | count: 1 96 | path: Classes/Service/SentryService.php 97 | 98 | - 99 | message: "#^Call to method finish\\(\\) on an unknown class Sentry\\\\Tracing\\\\Transaction\\.$#" 100 | count: 1 101 | path: Classes/Service/SentryService.php 102 | 103 | - 104 | message: "#^Call to method getOptions\\(\\) on an unknown class Sentry\\\\ClientInterface\\.$#" 105 | count: 1 106 | path: Classes/Service/SentryService.php 107 | 108 | - 109 | message: "#^Call to method setData\\(\\) on an unknown class Sentry\\\\Tracing\\\\Span\\.$#" 110 | count: 1 111 | path: Classes/Service/SentryService.php 112 | 113 | - 114 | message: "#^Call to method setDescription\\(\\) on an unknown class Sentry\\\\Tracing\\\\Span\\.$#" 115 | count: 1 116 | path: Classes/Service/SentryService.php 117 | 118 | - 119 | message: "#^Call to method setName\\(\\) on an unknown class Sentry\\\\Tracing\\\\TransactionContext\\.$#" 120 | count: 1 121 | path: Classes/Service/SentryService.php 122 | 123 | - 124 | message: "#^Call to method setOp\\(\\) on an unknown class Sentry\\\\Tracing\\\\Span\\.$#" 125 | count: 1 126 | path: Classes/Service/SentryService.php 127 | 128 | - 129 | message: "#^Call to method setOp\\(\\) on an unknown class Sentry\\\\Tracing\\\\TransactionContext\\.$#" 130 | count: 1 131 | path: Classes/Service/SentryService.php 132 | 133 | - 134 | message: "#^Call to method setParentSpanId\\(\\) on an unknown class Sentry\\\\Tracing\\\\Span\\.$#" 135 | count: 1 136 | path: Classes/Service/SentryService.php 137 | 138 | - 139 | message: "#^Call to method setStartTimestamp\\(\\) on an unknown class Sentry\\\\Tracing\\\\Span\\.$#" 140 | count: 1 141 | path: Classes/Service/SentryService.php 142 | 143 | - 144 | message: "#^Call to method setStartTimestamp\\(\\) on an unknown class Sentry\\\\Tracing\\\\Transaction\\.$#" 145 | count: 1 146 | path: Classes/Service/SentryService.php 147 | 148 | - 149 | message: "#^Call to method setStatus\\(\\) on an unknown class Sentry\\\\Tracing\\\\Transaction\\.$#" 150 | count: 2 151 | path: Classes/Service/SentryService.php 152 | 153 | - 154 | message: "#^Call to method startChild\\(\\) on an unknown class Sentry\\\\Tracing\\\\Span\\.$#" 155 | count: 1 156 | path: Classes/Service/SentryService.php 157 | 158 | - 159 | message: "#^Call to static method createFromHttpStatusCode\\(\\) on an unknown class Sentry\\\\Tracing\\\\SpanStatus\\.$#" 160 | count: 1 161 | path: Classes/Service/SentryService.php 162 | 163 | - 164 | message: "#^Call to static method getCurrentHub\\(\\) on an unknown class Sentry\\\\SentrySdk\\.$#" 165 | count: 3 166 | path: Classes/Service/SentryService.php 167 | 168 | - 169 | message: "#^Call to static method ok\\(\\) on an unknown class Sentry\\\\Tracing\\\\SpanStatus\\.$#" 170 | count: 1 171 | path: Classes/Service/SentryService.php 172 | 173 | - 174 | message: "#^Call to static method unknownError\\(\\) on an unknown class Sentry\\\\Tracing\\\\SpanStatus\\.$#" 175 | count: 1 176 | path: Classes/Service/SentryService.php 177 | 178 | - 179 | message: "#^Class Sentry\\\\Tracing\\\\Span not found\\.$#" 180 | count: 1 181 | path: Classes/Service/SentryService.php 182 | 183 | - 184 | message: "#^Function Sentry\\\\continueTrace not found\\.$#" 185 | count: 1 186 | path: Classes/Service/SentryService.php 187 | 188 | - 189 | message: "#^Function Sentry\\\\getBaggage not found\\.$#" 190 | count: 1 191 | path: Classes/Service/SentryService.php 192 | 193 | - 194 | message: "#^Function Sentry\\\\getTraceparent not found\\.$#" 195 | count: 1 196 | path: Classes/Service/SentryService.php 197 | 198 | - 199 | message: "#^Instantiated class Sentry\\\\Tracing\\\\SpanContext not found\\.$#" 200 | count: 2 201 | path: Classes/Service/SentryService.php 202 | 203 | - 204 | message: "#^Instantiated class Sentry\\\\Tracing\\\\TransactionContext not found\\.$#" 205 | count: 2 206 | path: Classes/Service/SentryService.php 207 | 208 | - 209 | message: "#^Method Kanti\\\\ServerTiming\\\\Service\\\\SentryService\\:\\:addResultToTransaction\\(\\) has invalid return type Sentry\\\\Tracing\\\\Transaction\\.$#" 210 | count: 1 211 | path: Classes/Service/SentryService.php 212 | 213 | - 214 | message: "#^Method Kanti\\\\ServerTiming\\\\Service\\\\SentryService\\:\\:createTransactionContextFromCli\\(\\) has invalid return type Sentry\\\\Tracing\\\\TransactionContext\\.$#" 215 | count: 1 216 | path: Classes/Service/SentryService.php 217 | 218 | - 219 | message: "#^Method Kanti\\\\ServerTiming\\\\Service\\\\SentryService\\:\\:createTransactionContextFromRequest\\(\\) has invalid return type Sentry\\\\Tracing\\\\TransactionContext\\.$#" 220 | count: 1 221 | path: Classes/Service/SentryService.php 222 | 223 | - 224 | message: "#^Method Kanti\\\\ServerTiming\\\\Service\\\\SentryService\\:\\:getSentryClient\\(\\) has invalid return type Sentry\\\\ClientInterface\\.$#" 225 | count: 1 226 | path: Classes/Service/SentryService.php 227 | 228 | - 229 | message: "#^Method Kanti\\\\ServerTiming\\\\Service\\\\SentryService\\:\\:startTransaction\\(\\) has invalid return type Sentry\\\\Tracing\\\\Transaction\\.$#" 230 | count: 1 231 | path: Classes/Service/SentryService.php 232 | 233 | - 234 | message: "#^PHPDoc tag @var for variable \\$stack contains unknown class Sentry\\\\Tracing\\\\Span\\.$#" 235 | count: 1 236 | path: Classes/Service/SentryService.php 237 | 238 | - 239 | message: "#^Parameter \\$span of method Kanti\\\\ServerTiming\\\\Service\\\\SentryService\\:\\:updateSpan\\(\\) has invalid type Sentry\\\\Tracing\\\\Span\\.$#" 240 | count: 1 241 | path: Classes/Service/SentryService.php 242 | 243 | - 244 | message: "#^Parameter \\$transaction of method Kanti\\\\ServerTiming\\\\Service\\\\SentryService\\:\\:addResultToTransaction\\(\\) has invalid type Sentry\\\\Tracing\\\\Transaction\\.$#" 245 | count: 1 246 | path: Classes/Service/SentryService.php 247 | 248 | - 249 | message: "#^Property Kanti\\\\ServerTiming\\\\Service\\\\SentryService\\:\\:\\$transaction has unknown class Sentry\\\\Tracing\\\\Transaction as its type\\.$#" 250 | count: 1 251 | path: Classes/Service/SentryService.php 252 | 253 | - 254 | message: "#^Used function Sentry\\\\continueTrace not found\\.$#" 255 | count: 1 256 | path: Classes/Service/SentryService.php 257 | 258 | - 259 | message: "#^Used function Sentry\\\\getBaggage not found\\.$#" 260 | count: 1 261 | path: Classes/Service/SentryService.php 262 | 263 | - 264 | message: "#^Used function Sentry\\\\getTraceparent not found\\.$#" 265 | count: 1 266 | path: Classes/Service/SentryService.php 267 | 268 | - 269 | message: "#^Call to an undefined method Doctrine\\\\DBAL\\\\Configuration\\:\\:getSQLLogger\\(\\)\\.$#" 270 | count: 1 271 | path: Classes/Middleware/AdminpanelSqlLoggingMiddleware.php 272 | 273 | - 274 | message: "#^Call to an undefined method Doctrine\\\\DBAL\\\\Configuration\\:\\:setSQLLogger\\(\\)\\.$#" 275 | count: 1 276 | path: Classes/Middleware/AdminpanelSqlLoggingMiddleware.php 277 | 278 | - 279 | message: "#^Instantiated class Doctrine\\\\DBAL\\\\Logging\\\\LoggerChain not found\\.$#" 280 | count: 1 281 | path: Classes/Middleware/AdminpanelSqlLoggingMiddleware.php 282 | 283 | - 284 | message: "#^Using nullsafe method call on non\\-nullable type Kanti\\\\ServerTiming\\\\Dto\\\\StopWatch\\. Use \\-\\> instead\\.$#" 285 | count: 1 286 | path: Classes/Middleware/WrapMiddleware.php 287 | 288 | - 289 | message: "#^Class TYPO3\\\\CMS\\\\Backend\\\\Http\\\\Application constructor invoked with 3 parameters, 2 required\\.$#" 290 | count: 1 291 | path: Classes/ServiceProvider.php 292 | 293 | - 294 | message: "#^Class TYPO3\\\\CMS\\\\Backend\\\\Http\\\\Application constructor invoked with 4 parameters, 2 required\\.$#" 295 | count: 1 296 | path: Classes/ServiceProvider.php 297 | 298 | - 299 | message: "#^Class TYPO3\\\\CMS\\\\Frontend\\\\Http\\\\Application constructor invoked with 3 parameters, 2 required\\.$#" 300 | count: 1 301 | path: Classes/ServiceProvider.php 302 | 303 | - 304 | message: "#^Class TYPO3\\\\CMS\\\\Frontend\\\\Http\\\\Application constructor invoked with 4 parameters, 2 required\\.$#" 305 | count: 1 306 | path: Classes/ServiceProvider.php 307 | 308 | - 309 | message: "#^Parameter \\#2 \\$context of class TYPO3\\\\CMS\\\\Backend\\\\Http\\\\Application constructor expects TYPO3\\\\CMS\\\\Core\\\\Context\\\\Context, TYPO3\\\\CMS\\\\Core\\\\Configuration\\\\ConfigurationManager given\\.$#" 310 | count: 2 311 | path: Classes/ServiceProvider.php 312 | 313 | - 314 | message: "#^Parameter \\#2 \\$context of class TYPO3\\\\CMS\\\\Frontend\\\\Http\\\\Application constructor expects TYPO3\\\\CMS\\\\Core\\\\Context\\\\Context, TYPO3\\\\CMS\\\\Core\\\\Configuration\\\\ConfigurationManager given\\.$#" 315 | count: 2 316 | path: Classes/ServiceProvider.php 317 | 318 | - 319 | message: "#^Method Doctrine\\\\DBAL\\\\Driver\\\\Middleware\\\\AbstractStatementMiddleware\\:\\:execute\\(\\) invoked with 1 parameter, 0 required\\.$#" 320 | count: 1 321 | path: Classes/SqlLogging/LoggingStatement.php 322 | 323 | - 324 | message: "#^Method Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingStatement\\:\\:execute\\(\\) has parameter \\$params with no type specified\\.$#" 325 | count: 1 326 | path: Classes/SqlLogging/LoggingStatement.php 327 | 328 | - 329 | message: "#^Call to an undefined method Doctrine\\\\DBAL\\\\Configuration\\:\\:setSQLLogger\\(\\)\\.$#" 330 | count: 1 331 | path: Classes/SqlLogging/SqlLoggerCore11.php 332 | 333 | - 334 | message: "#^Method class@anonymous/Classes/SqlLogging/SqlLoggerCore11\\.php\\:33\\:\\:startQuery\\(\\) has parameter \\$params with no value type specified in iterable type array\\.$#" 335 | count: 1 336 | path: Classes/SqlLogging/SqlLoggerCore11.php 337 | 338 | - 339 | message: "#^Method class@anonymous/Classes/SqlLogging/SqlLoggerCore11\\.php\\:33\\:\\:startQuery\\(\\) has parameter \\$sql with no type specified\\.$#" 340 | count: 1 341 | path: Classes/SqlLogging/SqlLoggerCore11.php 342 | 343 | - 344 | message: "#^Method class@anonymous/Classes/SqlLogging/SqlLoggerCore11\\.php\\:33\\:\\:startQuery\\(\\) has parameter \\$types with no value type specified in iterable type array\\.$#" 345 | count: 1 346 | path: Classes/SqlLogging/SqlLoggerCore11.php 347 | 348 | - 349 | message: "#^Call to method getMessage\\(\\) on an unknown class TYPO3\\\\CMS\\\\Core\\\\Mail\\\\Event\\\\BeforeMailerSentMessageEvent\\.$#" 350 | count: 1 351 | path: Classes/EventListener/MailEventListener.php 352 | 353 | - 354 | message: "#^Parameter \\$event of method Kanti\\\\ServerTiming\\\\EventListener\\\\MailEventListener\\:\\:start\\(\\) has invalid type TYPO3\\\\CMS\\\\Core\\\\Mail\\\\Event\\\\BeforeMailerSentMessageEvent\\.$#" 355 | count: 1 356 | path: Classes/EventListener/MailEventListener.php 357 | 358 | - 359 | message: "#^Parameter \\$event of method Kanti\\\\ServerTiming\\\\EventListener\\\\MailEventListener\\:\\:stop\\(\\) has invalid type TYPO3\\\\CMS\\\\Core\\\\Mail\\\\Event\\\\AfterMailerSentMessageEvent\\.$#" 360 | count: 1 361 | path: Classes/EventListener/MailEventListener.php 362 | 363 | - 364 | message: "#^Using nullsafe method call on non\\-nullable type Kanti\\\\ServerTiming\\\\Dto\\\\StopWatch\\. Use \\-\\> instead\\.$#" 365 | count: 1 366 | path: Classes/Middleware/WrapMiddleware.php 367 | 368 | - 369 | message: "#^Class TYPO3\\\\CMS\\\\Backend\\\\Http\\\\Application constructor invoked with 2 parameters, 3 required\\.$#" 370 | count: 1 371 | path: Classes/ServiceProvider.php 372 | 373 | - 374 | message: "#^Class TYPO3\\\\CMS\\\\Backend\\\\Http\\\\Application constructor invoked with 4 parameters, 3 required\\.$#" 375 | count: 1 376 | path: Classes/ServiceProvider.php 377 | 378 | - 379 | message: "#^Class TYPO3\\\\CMS\\\\Frontend\\\\Http\\\\Application constructor invoked with 2 parameters, 3 required\\.$#" 380 | count: 1 381 | path: Classes/ServiceProvider.php 382 | 383 | - 384 | message: "#^Class TYPO3\\\\CMS\\\\Frontend\\\\Http\\\\Application constructor invoked with 4 parameters, 3 required\\.$#" 385 | count: 1 386 | path: Classes/ServiceProvider.php 387 | 388 | - 389 | message: "#^Parameter \\#2 \\$configurationManager of class TYPO3\\\\CMS\\\\Backend\\\\Http\\\\Application constructor expects TYPO3\\\\CMS\\\\Core\\\\Configuration\\\\ConfigurationManager, TYPO3\\\\CMS\\\\Core\\\\Context\\\\Context given\\.$#" 390 | count: 1 391 | path: Classes/ServiceProvider.php 392 | 393 | - 394 | message: "#^Parameter \\#2 \\$configurationManager of class TYPO3\\\\CMS\\\\Frontend\\\\Http\\\\Application constructor expects TYPO3\\\\CMS\\\\Core\\\\Configuration\\\\ConfigurationManager, TYPO3\\\\CMS\\\\Core\\\\Context\\\\Context given\\.$#" 395 | count: 1 396 | path: Classes/ServiceProvider.php 397 | 398 | - 399 | message: "#^Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingConnection\\:\\:__construct\\(\\) calls parent\\:\\:__construct\\(\\) but Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingConnection does not extend any class\\.$#" 400 | count: 1 401 | path: Classes/SqlLogging/LoggingConnection.php 402 | 403 | - 404 | message: "#^Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingConnection\\:\\:exec\\(\\) calls parent\\:\\:exec\\(\\) but Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingConnection does not extend any class\\.$#" 405 | count: 1 406 | path: Classes/SqlLogging/LoggingConnection.php 407 | 408 | - 409 | message: "#^Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingConnection\\:\\:prepare\\(\\) calls parent\\:\\:prepare\\(\\) but Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingConnection does not extend any class\\.$#" 410 | count: 1 411 | path: Classes/SqlLogging/LoggingConnection.php 412 | 413 | - 414 | message: "#^Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingConnection\\:\\:query\\(\\) calls parent\\:\\:query\\(\\) but Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingConnection does not extend any class\\.$#" 415 | count: 1 416 | path: Classes/SqlLogging/LoggingConnection.php 417 | 418 | - 419 | message: "#^Method Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingConnection\\:\\:prepare\\(\\) return type has no value type specified in iterable type Doctrine\\\\DBAL\\\\Driver\\\\Statement\\.$#" 420 | count: 1 421 | path: Classes/SqlLogging/LoggingConnection.php 422 | 423 | - 424 | message: "#^Method Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingConnection\\:\\:prepare\\(\\) should return Doctrine\\\\DBAL\\\\Driver\\\\Statement but returns Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingStatement\\.$#" 425 | count: 1 426 | path: Classes/SqlLogging/LoggingConnection.php 427 | 428 | - 429 | message: "#^Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingDriver\\:\\:__construct\\(\\) calls parent\\:\\:__construct\\(\\) but Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingDriver does not extend any class\\.$#" 430 | count: 1 431 | path: Classes/SqlLogging/LoggingDriver.php 432 | 433 | - 434 | message: "#^Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingDriver\\:\\:connect\\(\\) calls parent\\:\\:connect\\(\\) but Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingDriver does not extend any class\\.$#" 435 | count: 1 436 | path: Classes/SqlLogging/LoggingDriver.php 437 | 438 | - 439 | message: "#^Method Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingDriver\\:\\:connect\\(\\) has parameter \\$params with no value type specified in iterable type array\\.$#" 440 | count: 1 441 | path: Classes/SqlLogging/LoggingDriver.php 442 | 443 | - 444 | message: "#^Method Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingDriver\\:\\:connect\\(\\) should return Doctrine\\\\DBAL\\\\Driver\\\\Connection but returns Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingConnection\\.$#" 445 | count: 1 446 | path: Classes/SqlLogging/LoggingDriver.php 447 | 448 | - 449 | message: "#^Method Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingMiddleware\\:\\:wrap\\(\\) should return Doctrine\\\\DBAL\\\\Driver but returns Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingDriver\\.$#" 450 | count: 1 451 | path: Classes/SqlLogging/LoggingMiddleware.php 452 | 453 | - 454 | message: "#^Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingStatement\\:\\:__construct\\(\\) calls parent\\:\\:__construct\\(\\) but Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingStatement does not extend any class\\.$#" 455 | count: 1 456 | path: Classes/SqlLogging/LoggingStatement.php 457 | 458 | - 459 | message: "#^Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingStatement\\:\\:execute\\(\\) calls parent\\:\\:execute\\(\\) but Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingStatement does not extend any class\\.$#" 460 | count: 1 461 | path: Classes/SqlLogging/LoggingStatement.php 462 | 463 | - 464 | message: "#^Method Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingStatement\\:\\:__construct\\(\\) has parameter \\$statement with no value type specified in iterable type Doctrine\\\\DBAL\\\\Driver\\\\Statement\\.$#" 465 | count: 1 466 | path: Classes/SqlLogging/LoggingStatement.php 467 | 468 | - 469 | message: "#^Method Kanti\\\\ServerTiming\\\\SqlLogging\\\\LoggingStatement\\:\\:execute\\(\\) has parameter \\$params with no type specified\\.$#" 470 | count: 1 471 | path: Classes/SqlLogging/LoggingStatement.php 472 | 473 | - 474 | message: '#^Property Kanti\\ServerTiming\\Dto\\StopWatch\:\:\$span has unknown class Sentry\\Tracing\\Span as its type\.$#' 475 | identifier: class.notFound 476 | count: 1 477 | path: Classes/Dto/StopWatch.php 478 | 479 | - 480 | message: '#^Call to an undefined method Doctrine\\DBAL\\Configuration\:\:getSQLLogger\(\)\.$#' 481 | identifier: method.notFound 482 | count: 1 483 | path: Classes/Middleware/AdminpanelSqlLoggingMiddleware.php 484 | 485 | - 486 | message: '#^Call to an undefined method Doctrine\\DBAL\\Configuration\:\:setSQLLogger\(\)\.$#' 487 | identifier: method.notFound 488 | count: 1 489 | path: Classes/Middleware/AdminpanelSqlLoggingMiddleware.php 490 | 491 | - 492 | message: '#^Instantiated class Doctrine\\DBAL\\Logging\\LoggerChain not found\.$#' 493 | identifier: class.notFound 494 | count: 1 495 | path: Classes/Middleware/AdminpanelSqlLoggingMiddleware.php 496 | 497 | - 498 | message: '#^Using nullsafe method call on non\-nullable type Kanti\\ServerTiming\\Dto\\StopWatch\. Use \-\> instead\.$#' 499 | identifier: nullsafe.neverNull 500 | count: 1 501 | path: Classes/Middleware/WrapMiddleware.php 502 | 503 | - 504 | message: '#^Call to method finish\(\) on an unknown class Sentry\\Tracing\\Span\.$#' 505 | identifier: class.notFound 506 | count: 1 507 | path: Classes/Service/SentryService.php 508 | 509 | - 510 | message: '#^Call to method finish\(\) on an unknown class Sentry\\Tracing\\Transaction\.$#' 511 | identifier: class.notFound 512 | count: 1 513 | path: Classes/Service/SentryService.php 514 | 515 | - 516 | message: '#^Call to method getOptions\(\) on an unknown class Sentry\\ClientInterface\.$#' 517 | identifier: class.notFound 518 | count: 1 519 | path: Classes/Service/SentryService.php 520 | 521 | - 522 | message: '#^Call to method setData\(\) on an unknown class Sentry\\Tracing\\Span\.$#' 523 | identifier: class.notFound 524 | count: 1 525 | path: Classes/Service/SentryService.php 526 | 527 | - 528 | message: '#^Call to method setDescription\(\) on an unknown class Sentry\\Tracing\\Span\.$#' 529 | identifier: class.notFound 530 | count: 1 531 | path: Classes/Service/SentryService.php 532 | 533 | - 534 | message: '#^Call to method setName\(\) on an unknown class Sentry\\Tracing\\TransactionContext\.$#' 535 | identifier: class.notFound 536 | count: 1 537 | path: Classes/Service/SentryService.php 538 | 539 | - 540 | message: '#^Call to method setOp\(\) on an unknown class Sentry\\Tracing\\Span\.$#' 541 | identifier: class.notFound 542 | count: 1 543 | path: Classes/Service/SentryService.php 544 | 545 | - 546 | message: '#^Call to method setOp\(\) on an unknown class Sentry\\Tracing\\TransactionContext\.$#' 547 | identifier: class.notFound 548 | count: 1 549 | path: Classes/Service/SentryService.php 550 | 551 | - 552 | message: '#^Call to method setParentSpanId\(\) on an unknown class Sentry\\Tracing\\Span\.$#' 553 | identifier: class.notFound 554 | count: 1 555 | path: Classes/Service/SentryService.php 556 | 557 | - 558 | message: '#^Call to method setStartTimestamp\(\) on an unknown class Sentry\\Tracing\\Span\.$#' 559 | identifier: class.notFound 560 | count: 1 561 | path: Classes/Service/SentryService.php 562 | 563 | - 564 | message: '#^Call to method setStartTimestamp\(\) on an unknown class Sentry\\Tracing\\Transaction\.$#' 565 | identifier: class.notFound 566 | count: 1 567 | path: Classes/Service/SentryService.php 568 | 569 | - 570 | message: '#^Call to method setStatus\(\) on an unknown class Sentry\\Tracing\\Transaction\.$#' 571 | identifier: class.notFound 572 | count: 2 573 | path: Classes/Service/SentryService.php 574 | 575 | - 576 | message: '#^Call to method startChild\(\) on an unknown class Sentry\\Tracing\\Span\.$#' 577 | identifier: class.notFound 578 | count: 1 579 | path: Classes/Service/SentryService.php 580 | 581 | - 582 | message: '#^Call to static method createFromHttpStatusCode\(\) on an unknown class Sentry\\Tracing\\SpanStatus\.$#' 583 | identifier: class.notFound 584 | count: 1 585 | path: Classes/Service/SentryService.php 586 | 587 | - 588 | message: '#^Call to static method getCurrentHub\(\) on an unknown class Sentry\\SentrySdk\.$#' 589 | identifier: class.notFound 590 | count: 3 591 | path: Classes/Service/SentryService.php 592 | 593 | - 594 | message: '#^Call to static method ok\(\) on an unknown class Sentry\\Tracing\\SpanStatus\.$#' 595 | identifier: class.notFound 596 | count: 1 597 | path: Classes/Service/SentryService.php 598 | 599 | - 600 | message: '#^Call to static method unknownError\(\) on an unknown class Sentry\\Tracing\\SpanStatus\.$#' 601 | identifier: class.notFound 602 | count: 1 603 | path: Classes/Service/SentryService.php 604 | 605 | - 606 | message: '#^Class Sentry\\Tracing\\Span not found\.$#' 607 | identifier: class.notFound 608 | count: 1 609 | path: Classes/Service/SentryService.php 610 | 611 | - 612 | message: '#^Function Sentry\\continueTrace not found\.$#' 613 | identifier: function.notFound 614 | count: 1 615 | path: Classes/Service/SentryService.php 616 | 617 | - 618 | message: '#^Function Sentry\\getBaggage not found\.$#' 619 | identifier: function.notFound 620 | count: 1 621 | path: Classes/Service/SentryService.php 622 | 623 | - 624 | message: '#^Function Sentry\\getTraceparent not found\.$#' 625 | identifier: function.notFound 626 | count: 1 627 | path: Classes/Service/SentryService.php 628 | 629 | - 630 | message: '#^Instantiated class Sentry\\Tracing\\SpanContext not found\.$#' 631 | identifier: class.notFound 632 | count: 2 633 | path: Classes/Service/SentryService.php 634 | 635 | - 636 | message: '#^Instantiated class Sentry\\Tracing\\TransactionContext not found\.$#' 637 | identifier: class.notFound 638 | count: 2 639 | path: Classes/Service/SentryService.php 640 | 641 | - 642 | message: '#^Method Kanti\\ServerTiming\\Service\\SentryService\:\:addResultToTransaction\(\) has invalid return type Sentry\\Tracing\\Transaction\.$#' 643 | identifier: class.notFound 644 | count: 1 645 | path: Classes/Service/SentryService.php 646 | 647 | - 648 | message: '#^Method Kanti\\ServerTiming\\Service\\SentryService\:\:createTransactionContextFromCli\(\) has invalid return type Sentry\\Tracing\\TransactionContext\.$#' 649 | identifier: class.notFound 650 | count: 1 651 | path: Classes/Service/SentryService.php 652 | 653 | - 654 | message: '#^Method Kanti\\ServerTiming\\Service\\SentryService\:\:createTransactionContextFromRequest\(\) has invalid return type Sentry\\Tracing\\TransactionContext\.$#' 655 | identifier: class.notFound 656 | count: 1 657 | path: Classes/Service/SentryService.php 658 | 659 | - 660 | message: '#^Method Kanti\\ServerTiming\\Service\\SentryService\:\:getSentryClient\(\) has invalid return type Sentry\\ClientInterface\.$#' 661 | identifier: class.notFound 662 | count: 1 663 | path: Classes/Service/SentryService.php 664 | 665 | - 666 | message: '#^Method Kanti\\ServerTiming\\Service\\SentryService\:\:startTransaction\(\) has invalid return type Sentry\\Tracing\\Transaction\.$#' 667 | identifier: class.notFound 668 | count: 1 669 | path: Classes/Service/SentryService.php 670 | 671 | - 672 | message: '#^Method Kanti\\ServerTiming\\Service\\SentryService\:\:startTransaction\(\) should return Sentry\\Tracing\\Transaction but returns Sentry\\Tracing\\Transaction\|null\.$#' 673 | identifier: return.type 674 | count: 1 675 | path: Classes/Service/SentryService.php 676 | 677 | - 678 | message: '#^PHPDoc tag @var for variable \$stack contains unknown class Sentry\\Tracing\\Span\.$#' 679 | identifier: class.notFound 680 | count: 1 681 | path: Classes/Service/SentryService.php 682 | 683 | - 684 | message: '#^Parameter \$span of method Kanti\\ServerTiming\\Service\\SentryService\:\:updateSpan\(\) has invalid type Sentry\\Tracing\\Span\.$#' 685 | identifier: class.notFound 686 | count: 1 687 | path: Classes/Service/SentryService.php 688 | 689 | - 690 | message: '#^Parameter \$transaction of method Kanti\\ServerTiming\\Service\\SentryService\:\:addResultToTransaction\(\) has invalid type Sentry\\Tracing\\Transaction\.$#' 691 | identifier: class.notFound 692 | count: 1 693 | path: Classes/Service/SentryService.php 694 | 695 | - 696 | message: '#^Property Kanti\\ServerTiming\\Service\\SentryService\:\:\$transaction has unknown class Sentry\\Tracing\\Transaction as its type\.$#' 697 | identifier: class.notFound 698 | count: 1 699 | path: Classes/Service/SentryService.php 700 | 701 | - 702 | message: '#^Used function Sentry\\continueTrace not found\.$#' 703 | identifier: function.notFound 704 | count: 1 705 | path: Classes/Service/SentryService.php 706 | 707 | - 708 | message: '#^Used function Sentry\\getBaggage not found\.$#' 709 | identifier: function.notFound 710 | count: 1 711 | path: Classes/Service/SentryService.php 712 | 713 | - 714 | message: '#^Used function Sentry\\getTraceparent not found\.$#' 715 | identifier: function.notFound 716 | count: 1 717 | path: Classes/Service/SentryService.php 718 | 719 | - 720 | message: '#^Class TYPO3\\CMS\\Backend\\Http\\Application constructor invoked with 3 parameters, 2 required\.$#' 721 | identifier: arguments.count 722 | count: 1 723 | path: Classes/ServiceProvider.php 724 | 725 | - 726 | message: '#^Class TYPO3\\CMS\\Backend\\Http\\Application constructor invoked with 4 parameters, 2 required\.$#' 727 | identifier: arguments.count 728 | count: 1 729 | path: Classes/ServiceProvider.php 730 | 731 | - 732 | message: '#^Class TYPO3\\CMS\\Frontend\\Http\\Application constructor invoked with 3 parameters, 2 required\.$#' 733 | identifier: arguments.count 734 | count: 1 735 | path: Classes/ServiceProvider.php 736 | 737 | - 738 | message: '#^Class TYPO3\\CMS\\Frontend\\Http\\Application constructor invoked with 4 parameters, 2 required\.$#' 739 | identifier: arguments.count 740 | count: 1 741 | path: Classes/ServiceProvider.php 742 | 743 | - 744 | message: '#^Parameter \#2 \$context of class TYPO3\\CMS\\Backend\\Http\\Application constructor expects TYPO3\\CMS\\Core\\Context\\Context, TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager given\.$#' 745 | identifier: argument.type 746 | count: 2 747 | path: Classes/ServiceProvider.php 748 | 749 | - 750 | message: '#^Parameter \#2 \$context of class TYPO3\\CMS\\Frontend\\Http\\Application constructor expects TYPO3\\CMS\\Core\\Context\\Context, TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager given\.$#' 751 | identifier: argument.type 752 | count: 2 753 | path: Classes/ServiceProvider.php 754 | 755 | - 756 | message: '#^Method Doctrine\\DBAL\\Driver\\Middleware\\AbstractStatementMiddleware\:\:execute\(\) invoked with 1 parameter, 0 required\.$#' 757 | identifier: arguments.count 758 | count: 1 759 | path: Classes/SqlLogging/LoggingStatement.php 760 | 761 | - 762 | message: '#^Method Kanti\\ServerTiming\\SqlLogging\\LoggingStatement\:\:execute\(\) has parameter \$params with no type specified\.$#' 763 | identifier: missingType.parameter 764 | count: 1 765 | path: Classes/SqlLogging/LoggingStatement.php 766 | 767 | - 768 | message: '#^Method Kanti\\ServerTiming\\Utility\\TimingUtility\:\:chunkStringArray\(\) should return list\ but returns array\, string\>\.$#' 769 | identifier: return.type 770 | count: 1 771 | path: Classes/Utility/TimingUtility.php 772 | 773 | - 774 | message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertIsFloat\(\) with float will always evaluate to true\.$#' 775 | identifier: staticMethod.alreadyNarrowedType 776 | count: 3 777 | path: Tests/TimingUtilityTest.php 778 | 779 | - 780 | message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertIsFloat\(\) with null will always evaluate to false\.$#' 781 | identifier: staticMethod.impossibleType 782 | count: 1 783 | path: Tests/TimingUtilityTest.php 784 | 785 | - 786 | message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertIsList\(\) with list\ will always evaluate to true\.$#' 787 | identifier: staticMethod.alreadyNarrowedType 788 | count: 1 789 | path: Tests/TimingUtilityTest.php 790 | 791 | - 792 | message: '#^Method Kanti\\ServerTiming\\Service\\SentryServiceInterface@anonymous/Tests/TimingUtilityTest\.php\:[0-9]+\:\:sendSentryTrace\(\) never returns null so it can be removed from the return type\.$#' 793 | identifier: return.unusedType 794 | count: 1 795 | path: Tests/TimingUtilityTest.php 796 | 797 | - 798 | message: '#^Parameter \#1 \$expected of static method PHPUnit\\Framework\\Assert\:\:assertSame\(\) contains unresolvable type\.$#' 799 | identifier: argument.unresolvableType 800 | count: 1 801 | path: Tests/TimingUtilityTest.php 802 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | max_line_length = 160 10 | tab_width = 2 11 | ij_continuation_indent_size = 2 12 | ij_formatter_off_tag = @formatter:off 13 | ij_formatter_on_tag = @formatter:on 14 | ij_formatter_tags_enabled = false 15 | ij_smart_tabs = false 16 | ij_visual_guides = 120,160 17 | ij_wrap_on_typing = false 18 | 19 | [*.css] 20 | ij_css_align_closing_brace_with_properties = false 21 | ij_css_blank_lines_around_nested_selector = 1 22 | ij_css_blank_lines_between_blocks = 1 23 | ij_css_block_comment_add_space = false 24 | ij_css_brace_placement = end_of_line 25 | ij_css_enforce_quotes_on_format = true 26 | ij_css_hex_color_long_format = true 27 | ij_css_hex_color_lower_case = true 28 | ij_css_hex_color_short_format = false 29 | ij_css_hex_color_upper_case = false 30 | ij_css_keep_blank_lines_in_code = 2 31 | ij_css_keep_indents_on_empty_lines = false 32 | ij_css_keep_single_line_blocks = false 33 | ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow 34 | ij_css_space_after_colon = true 35 | ij_css_space_before_opening_brace = true 36 | ij_css_use_double_quotes = false 37 | ij_css_value_alignment = do_not_align 38 | 39 | [*.less] 40 | ij_less_align_closing_brace_with_properties = false 41 | ij_less_blank_lines_around_nested_selector = 1 42 | ij_less_blank_lines_between_blocks = 1 43 | ij_less_block_comment_add_space = false 44 | ij_less_brace_placement = 0 45 | ij_less_enforce_quotes_on_format = true 46 | ij_less_hex_color_long_format = false 47 | ij_less_hex_color_lower_case = false 48 | ij_less_hex_color_short_format = false 49 | ij_less_hex_color_upper_case = false 50 | ij_less_keep_blank_lines_in_code = 2 51 | ij_less_keep_indents_on_empty_lines = false 52 | ij_less_keep_single_line_blocks = false 53 | ij_less_line_comment_add_space = false 54 | ij_less_line_comment_at_first_column = false 55 | ij_less_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow 56 | ij_less_space_after_colon = true 57 | ij_less_space_before_opening_brace = true 58 | ij_less_use_double_quotes = false 59 | ij_less_value_alignment = 0 60 | 61 | [*.twig] 62 | ij_twig_keep_indents_on_empty_lines = false 63 | ij_twig_spaces_inside_comments_delimiters = true 64 | ij_twig_spaces_inside_delimiters = true 65 | ij_twig_spaces_inside_variable_delimiters = true 66 | 67 | [{*.cjs,*.js}] 68 | ij_javascript_align_imports = false 69 | ij_javascript_align_multiline_array_initializer_expression = false 70 | ij_javascript_align_multiline_binary_operation = false 71 | ij_javascript_align_multiline_chained_methods = false 72 | ij_javascript_align_multiline_extends_list = false 73 | ij_javascript_align_multiline_for = true 74 | ij_javascript_align_multiline_parameters = true 75 | ij_javascript_align_multiline_parameters_in_calls = false 76 | ij_javascript_align_multiline_ternary_operation = false 77 | ij_javascript_align_object_properties = 0 78 | ij_javascript_align_union_types = false 79 | ij_javascript_align_var_statements = 0 80 | ij_javascript_array_initializer_new_line_after_left_brace = false 81 | ij_javascript_array_initializer_right_brace_on_new_line = false 82 | ij_javascript_array_initializer_wrap = off 83 | ij_javascript_assignment_wrap = off 84 | ij_javascript_binary_operation_sign_on_next_line = false 85 | ij_javascript_binary_operation_wrap = off 86 | ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** 87 | ij_javascript_blank_lines_after_imports = 1 88 | ij_javascript_blank_lines_around_class = 1 89 | ij_javascript_blank_lines_around_field = 0 90 | ij_javascript_blank_lines_around_function = 1 91 | ij_javascript_blank_lines_around_method = 1 92 | ij_javascript_block_brace_style = end_of_line 93 | ij_javascript_block_comment_add_space = false 94 | ij_javascript_block_comment_at_first_column = true 95 | ij_javascript_call_parameters_new_line_after_left_paren = false 96 | ij_javascript_call_parameters_right_paren_on_new_line = false 97 | ij_javascript_call_parameters_wrap = off 98 | ij_javascript_catch_on_new_line = false 99 | ij_javascript_chained_call_dot_on_new_line = true 100 | ij_javascript_class_brace_style = end_of_line 101 | ij_javascript_comma_on_new_line = false 102 | ij_javascript_do_while_brace_force = never 103 | ij_javascript_else_on_new_line = false 104 | ij_javascript_enforce_trailing_comma = whenmultiline 105 | ij_javascript_extends_keyword_wrap = off 106 | ij_javascript_extends_list_wrap = off 107 | ij_javascript_field_prefix = _ 108 | ij_javascript_file_name_style = relaxed 109 | ij_javascript_finally_on_new_line = false 110 | ij_javascript_for_brace_force = never 111 | ij_javascript_for_statement_new_line_after_left_paren = false 112 | ij_javascript_for_statement_right_paren_on_new_line = false 113 | ij_javascript_for_statement_wrap = off 114 | ij_javascript_force_quote_style = false 115 | ij_javascript_force_semicolon_style = true 116 | ij_javascript_function_expression_brace_style = end_of_line 117 | ij_javascript_if_brace_force = never 118 | ij_javascript_import_merge_members = global 119 | ij_javascript_import_prefer_absolute_path = global 120 | ij_javascript_import_sort_members = true 121 | ij_javascript_import_sort_module_name = false 122 | ij_javascript_import_use_node_resolution = true 123 | ij_javascript_imports_wrap = on_every_item 124 | ij_javascript_indent_case_from_switch = true 125 | ij_javascript_indent_chained_calls = true 126 | ij_javascript_indent_package_children = 0 127 | ij_javascript_jsx_attribute_value = braces 128 | ij_javascript_keep_blank_lines_in_code = 2 129 | ij_javascript_keep_first_column_comment = true 130 | ij_javascript_keep_indents_on_empty_lines = false 131 | ij_javascript_keep_line_breaks = true 132 | ij_javascript_keep_simple_blocks_in_one_line = false 133 | ij_javascript_keep_simple_methods_in_one_line = false 134 | ij_javascript_line_comment_add_space = true 135 | ij_javascript_line_comment_at_first_column = false 136 | ij_javascript_method_brace_style = end_of_line 137 | ij_javascript_method_call_chain_wrap = off 138 | ij_javascript_method_parameters_new_line_after_left_paren = false 139 | ij_javascript_method_parameters_right_paren_on_new_line = false 140 | ij_javascript_method_parameters_wrap = off 141 | ij_javascript_object_literal_wrap = on_every_item 142 | ij_javascript_parentheses_expression_new_line_after_left_paren = false 143 | ij_javascript_parentheses_expression_right_paren_on_new_line = false 144 | ij_javascript_place_assignment_sign_on_next_line = false 145 | ij_javascript_prefer_as_type_cast = false 146 | ij_javascript_prefer_explicit_types_function_expression_returns = false 147 | ij_javascript_prefer_explicit_types_function_returns = false 148 | ij_javascript_prefer_explicit_types_vars_fields = false 149 | ij_javascript_prefer_parameters_wrap = false 150 | ij_javascript_reformat_c_style_comments = false 151 | ij_javascript_space_after_colon = true 152 | ij_javascript_space_after_comma = true 153 | ij_javascript_space_after_dots_in_rest_parameter = false 154 | ij_javascript_space_after_generator_mult = true 155 | ij_javascript_space_after_property_colon = true 156 | ij_javascript_space_after_quest = true 157 | ij_javascript_space_after_type_colon = true 158 | ij_javascript_space_after_unary_not = false 159 | ij_javascript_space_before_async_arrow_lparen = true 160 | ij_javascript_space_before_catch_keyword = true 161 | ij_javascript_space_before_catch_left_brace = true 162 | ij_javascript_space_before_catch_parentheses = true 163 | ij_javascript_space_before_class_lbrace = true 164 | ij_javascript_space_before_class_left_brace = true 165 | ij_javascript_space_before_colon = true 166 | ij_javascript_space_before_comma = false 167 | ij_javascript_space_before_do_left_brace = true 168 | ij_javascript_space_before_else_keyword = true 169 | ij_javascript_space_before_else_left_brace = true 170 | ij_javascript_space_before_finally_keyword = true 171 | ij_javascript_space_before_finally_left_brace = true 172 | ij_javascript_space_before_for_left_brace = true 173 | ij_javascript_space_before_for_parentheses = true 174 | ij_javascript_space_before_for_semicolon = false 175 | ij_javascript_space_before_function_left_parenth = true 176 | ij_javascript_space_before_generator_mult = false 177 | ij_javascript_space_before_if_left_brace = true 178 | ij_javascript_space_before_if_parentheses = true 179 | ij_javascript_space_before_method_call_parentheses = false 180 | ij_javascript_space_before_method_left_brace = true 181 | ij_javascript_space_before_method_parentheses = false 182 | ij_javascript_space_before_property_colon = false 183 | ij_javascript_space_before_quest = true 184 | ij_javascript_space_before_switch_left_brace = true 185 | ij_javascript_space_before_switch_parentheses = true 186 | ij_javascript_space_before_try_left_brace = true 187 | ij_javascript_space_before_type_colon = false 188 | ij_javascript_space_before_unary_not = false 189 | ij_javascript_space_before_while_keyword = true 190 | ij_javascript_space_before_while_left_brace = true 191 | ij_javascript_space_before_while_parentheses = true 192 | ij_javascript_spaces_around_additive_operators = true 193 | ij_javascript_spaces_around_arrow_function_operator = true 194 | ij_javascript_spaces_around_assignment_operators = true 195 | ij_javascript_spaces_around_bitwise_operators = true 196 | ij_javascript_spaces_around_equality_operators = true 197 | ij_javascript_spaces_around_logical_operators = true 198 | ij_javascript_spaces_around_multiplicative_operators = true 199 | ij_javascript_spaces_around_relational_operators = true 200 | ij_javascript_spaces_around_shift_operators = true 201 | ij_javascript_spaces_around_unary_operator = false 202 | ij_javascript_spaces_within_array_initializer_brackets = false 203 | ij_javascript_spaces_within_brackets = false 204 | ij_javascript_spaces_within_catch_parentheses = false 205 | ij_javascript_spaces_within_for_parentheses = false 206 | ij_javascript_spaces_within_if_parentheses = false 207 | ij_javascript_spaces_within_imports = true 208 | ij_javascript_spaces_within_interpolation_expressions = false 209 | ij_javascript_spaces_within_method_call_parentheses = false 210 | ij_javascript_spaces_within_method_parentheses = false 211 | ij_javascript_spaces_within_object_literal_braces = false 212 | ij_javascript_spaces_within_object_type_braces = true 213 | ij_javascript_spaces_within_parentheses = false 214 | ij_javascript_spaces_within_switch_parentheses = false 215 | ij_javascript_spaces_within_type_assertion = false 216 | ij_javascript_spaces_within_union_types = true 217 | ij_javascript_spaces_within_while_parentheses = false 218 | ij_javascript_special_else_if_treatment = true 219 | ij_javascript_ternary_operation_signs_on_next_line = false 220 | ij_javascript_ternary_operation_wrap = off 221 | ij_javascript_union_types_wrap = on_every_item 222 | ij_javascript_use_chained_calls_group_indents = false 223 | ij_javascript_use_double_quotes = true 224 | ij_javascript_use_explicit_js_extension = auto 225 | ij_javascript_use_path_mapping = always 226 | ij_javascript_use_public_modifier = false 227 | ij_javascript_use_semicolon_after_statement = true 228 | ij_javascript_var_declaration_wrap = normal 229 | ij_javascript_while_brace_force = never 230 | ij_javascript_while_on_new_line = false 231 | ij_javascript_wrap_comments = false 232 | 233 | [{*.htm,*.shtml,*.sht,*.ng,*.shtm,*.html}] 234 | ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 235 | ij_html_align_attributes = true 236 | ij_html_align_text = false 237 | ij_html_attribute_wrap = off 238 | ij_html_block_comment_add_space = false 239 | ij_html_block_comment_at_first_column = true 240 | ij_html_do_not_align_children_of_min_lines = 0 241 | ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p 242 | ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot 243 | ij_html_enforce_quotes = false 244 | ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var 245 | ij_html_keep_blank_lines = 2 246 | ij_html_keep_indents_on_empty_lines = false 247 | ij_html_keep_line_breaks = true 248 | ij_html_keep_line_breaks_in_text = true 249 | ij_html_keep_whitespaces = false 250 | ij_html_keep_whitespaces_inside = span,pre,textarea 251 | ij_html_line_comment_at_first_column = true 252 | ij_html_new_line_after_last_attribute = never 253 | ij_html_new_line_before_first_attribute = never 254 | ij_html_quote_style = double 255 | ij_html_remove_new_line_before_tags = br 256 | ij_html_space_after_tag_name = false 257 | ij_html_space_around_equality_in_attribute = false 258 | ij_html_space_inside_empty_tag = false 259 | ij_html_text_wrap = normal 260 | 261 | [{*.yml,*.yaml}] 262 | ij_yaml_align_values_properties = do_not_align 263 | ij_yaml_autoinsert_sequence_marker = true 264 | ij_yaml_block_mapping_on_new_line = false 265 | ij_yaml_indent_sequence_value = true 266 | ij_yaml_keep_indents_on_empty_lines = false 267 | ij_yaml_keep_line_breaks = true 268 | ij_yaml_sequence_on_new_line = false 269 | ij_yaml_space_before_colon = false 270 | ij_yaml_spaces_within_braces = true 271 | ij_yaml_spaces_within_brackets = true 272 | 273 | [{.prettierrc,.stylelintrc,.eslintrc,.babelrc,jest.config,*.json,*.bowerrc,*.jsb3,*.jsb2,*.lock,*.graphqlconfig,*.har,bowerrc}] 274 | ij_json_keep_blank_lines_in_code = 0 275 | ij_json_keep_indents_on_empty_lines = false 276 | ij_json_keep_line_breaks = true 277 | ij_json_space_after_colon = true 278 | ij_json_space_after_comma = true 279 | ij_json_space_before_colon = false 280 | ij_json_space_before_comma = false 281 | ij_json_spaces_within_braces = false 282 | ij_json_spaces_within_brackets = false 283 | ij_json_wrap_long_lines = false 284 | 285 | [{*.ats,*.cts,*.mts,*.ts}] 286 | ij_typescript_align_imports = false 287 | ij_typescript_align_multiline_array_initializer_expression = false 288 | ij_typescript_align_multiline_binary_operation = false 289 | ij_typescript_align_multiline_chained_methods = false 290 | ij_typescript_align_multiline_extends_list = false 291 | ij_typescript_align_multiline_for = true 292 | ij_typescript_align_multiline_parameters = true 293 | ij_typescript_align_multiline_parameters_in_calls = false 294 | ij_typescript_align_multiline_ternary_operation = false 295 | ij_typescript_align_object_properties = 0 296 | ij_typescript_align_union_types = false 297 | ij_typescript_align_var_statements = 0 298 | ij_typescript_array_initializer_new_line_after_left_brace = false 299 | ij_typescript_array_initializer_right_brace_on_new_line = false 300 | ij_typescript_array_initializer_wrap = off 301 | ij_typescript_assignment_wrap = off 302 | ij_typescript_binary_operation_sign_on_next_line = false 303 | ij_typescript_binary_operation_wrap = off 304 | ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** 305 | ij_typescript_blank_lines_after_imports = 1 306 | ij_typescript_blank_lines_around_class = 1 307 | ij_typescript_blank_lines_around_field = 0 308 | ij_typescript_blank_lines_around_field_in_interface = 0 309 | ij_typescript_blank_lines_around_function = 1 310 | ij_typescript_blank_lines_around_method = 1 311 | ij_typescript_blank_lines_around_method_in_interface = 1 312 | ij_typescript_block_brace_style = end_of_line 313 | ij_typescript_block_comment_add_space = false 314 | ij_typescript_block_comment_at_first_column = true 315 | ij_typescript_call_parameters_new_line_after_left_paren = false 316 | ij_typescript_call_parameters_right_paren_on_new_line = false 317 | ij_typescript_call_parameters_wrap = off 318 | ij_typescript_catch_on_new_line = false 319 | ij_typescript_chained_call_dot_on_new_line = true 320 | ij_typescript_class_brace_style = end_of_line 321 | ij_typescript_comma_on_new_line = false 322 | ij_typescript_do_while_brace_force = never 323 | ij_typescript_else_on_new_line = false 324 | ij_typescript_enforce_trailing_comma = whenmultiline 325 | ij_typescript_enum_constants_wrap = on_every_item 326 | ij_typescript_extends_keyword_wrap = off 327 | ij_typescript_extends_list_wrap = off 328 | ij_typescript_field_prefix = _ 329 | ij_typescript_file_name_style = relaxed 330 | ij_typescript_finally_on_new_line = false 331 | ij_typescript_for_brace_force = never 332 | ij_typescript_for_statement_new_line_after_left_paren = false 333 | ij_typescript_for_statement_right_paren_on_new_line = false 334 | ij_typescript_for_statement_wrap = off 335 | ij_typescript_force_quote_style = true 336 | ij_typescript_force_semicolon_style = true 337 | ij_typescript_function_expression_brace_style = end_of_line 338 | ij_typescript_if_brace_force = never 339 | ij_typescript_import_merge_members = global 340 | ij_typescript_import_prefer_absolute_path = global 341 | ij_typescript_import_sort_members = true 342 | ij_typescript_import_sort_module_name = false 343 | ij_typescript_import_use_node_resolution = true 344 | ij_typescript_imports_wrap = on_every_item 345 | ij_typescript_indent_case_from_switch = true 346 | ij_typescript_indent_chained_calls = true 347 | ij_typescript_indent_package_children = 0 348 | ij_typescript_jsdoc_include_types = false 349 | ij_typescript_jsx_attribute_value = braces 350 | ij_typescript_keep_blank_lines_in_code = 1 351 | ij_typescript_keep_first_column_comment = true 352 | ij_typescript_keep_indents_on_empty_lines = false 353 | ij_typescript_keep_line_breaks = true 354 | ij_typescript_keep_simple_blocks_in_one_line = false 355 | ij_typescript_keep_simple_methods_in_one_line = false 356 | ij_typescript_line_comment_add_space = true 357 | ij_typescript_line_comment_at_first_column = false 358 | ij_typescript_method_brace_style = end_of_line 359 | ij_typescript_method_call_chain_wrap = off 360 | ij_typescript_method_parameters_new_line_after_left_paren = false 361 | ij_typescript_method_parameters_right_paren_on_new_line = false 362 | ij_typescript_method_parameters_wrap = off 363 | ij_typescript_object_literal_wrap = on_every_item 364 | ij_typescript_parentheses_expression_new_line_after_left_paren = false 365 | ij_typescript_parentheses_expression_right_paren_on_new_line = false 366 | ij_typescript_place_assignment_sign_on_next_line = false 367 | ij_typescript_prefer_as_type_cast = false 368 | ij_typescript_prefer_explicit_types_function_expression_returns = false 369 | ij_typescript_prefer_explicit_types_function_returns = false 370 | ij_typescript_prefer_explicit_types_vars_fields = false 371 | ij_typescript_prefer_parameters_wrap = false 372 | ij_typescript_reformat_c_style_comments = false 373 | ij_typescript_space_after_colon = true 374 | ij_typescript_space_after_comma = true 375 | ij_typescript_space_after_dots_in_rest_parameter = false 376 | ij_typescript_space_after_generator_mult = true 377 | ij_typescript_space_after_property_colon = true 378 | ij_typescript_space_after_quest = true 379 | ij_typescript_space_after_type_colon = true 380 | ij_typescript_space_after_unary_not = false 381 | ij_typescript_space_before_async_arrow_lparen = true 382 | ij_typescript_space_before_catch_keyword = true 383 | ij_typescript_space_before_catch_left_brace = true 384 | ij_typescript_space_before_catch_parentheses = true 385 | ij_typescript_space_before_class_lbrace = true 386 | ij_typescript_space_before_class_left_brace = true 387 | ij_typescript_space_before_colon = true 388 | ij_typescript_space_before_comma = false 389 | ij_typescript_space_before_do_left_brace = true 390 | ij_typescript_space_before_else_keyword = true 391 | ij_typescript_space_before_else_left_brace = true 392 | ij_typescript_space_before_finally_keyword = true 393 | ij_typescript_space_before_finally_left_brace = true 394 | ij_typescript_space_before_for_left_brace = true 395 | ij_typescript_space_before_for_parentheses = true 396 | ij_typescript_space_before_for_semicolon = false 397 | ij_typescript_space_before_function_left_parenth = true 398 | ij_typescript_space_before_generator_mult = false 399 | ij_typescript_space_before_if_left_brace = true 400 | ij_typescript_space_before_if_parentheses = true 401 | ij_typescript_space_before_method_call_parentheses = false 402 | ij_typescript_space_before_method_left_brace = true 403 | ij_typescript_space_before_method_parentheses = false 404 | ij_typescript_space_before_property_colon = false 405 | ij_typescript_space_before_quest = true 406 | ij_typescript_space_before_switch_left_brace = true 407 | ij_typescript_space_before_switch_parentheses = true 408 | ij_typescript_space_before_try_left_brace = true 409 | ij_typescript_space_before_type_colon = false 410 | ij_typescript_space_before_unary_not = false 411 | ij_typescript_space_before_while_keyword = true 412 | ij_typescript_space_before_while_left_brace = true 413 | ij_typescript_space_before_while_parentheses = true 414 | ij_typescript_spaces_around_additive_operators = true 415 | ij_typescript_spaces_around_arrow_function_operator = true 416 | ij_typescript_spaces_around_assignment_operators = true 417 | ij_typescript_spaces_around_bitwise_operators = true 418 | ij_typescript_spaces_around_equality_operators = true 419 | ij_typescript_spaces_around_logical_operators = true 420 | ij_typescript_spaces_around_multiplicative_operators = true 421 | ij_typescript_spaces_around_relational_operators = true 422 | ij_typescript_spaces_around_shift_operators = true 423 | ij_typescript_spaces_around_unary_operator = false 424 | ij_typescript_spaces_within_array_initializer_brackets = false 425 | ij_typescript_spaces_within_brackets = false 426 | ij_typescript_spaces_within_catch_parentheses = false 427 | ij_typescript_spaces_within_for_parentheses = false 428 | ij_typescript_spaces_within_if_parentheses = false 429 | ij_typescript_spaces_within_imports = true 430 | ij_typescript_spaces_within_interpolation_expressions = false 431 | ij_typescript_spaces_within_method_call_parentheses = false 432 | ij_typescript_spaces_within_method_parentheses = false 433 | ij_typescript_spaces_within_object_literal_braces = false 434 | ij_typescript_spaces_within_object_type_braces = true 435 | ij_typescript_spaces_within_parentheses = false 436 | ij_typescript_spaces_within_switch_parentheses = false 437 | ij_typescript_spaces_within_type_assertion = false 438 | ij_typescript_spaces_within_union_types = true 439 | ij_typescript_spaces_within_while_parentheses = false 440 | ij_typescript_special_else_if_treatment = true 441 | ij_typescript_ternary_operation_signs_on_next_line = false 442 | ij_typescript_ternary_operation_wrap = off 443 | ij_typescript_union_types_wrap = on_every_item 444 | ij_typescript_use_chained_calls_group_indents = false 445 | ij_typescript_use_double_quotes = false 446 | ij_typescript_use_explicit_js_extension = auto 447 | ij_typescript_use_path_mapping = always 448 | ij_typescript_use_public_modifier = false 449 | ij_typescript_use_semicolon_after_statement = true 450 | ij_typescript_var_declaration_wrap = normal 451 | ij_typescript_while_brace_force = never 452 | ij_typescript_while_on_new_line = false 453 | ij_typescript_wrap_comments = false 454 | 455 | [{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xlf,*.xml,*.xsd,*.xsl,*.xslt,*.xul,*.xml.dist}] 456 | ij_xml_align_attributes = false 457 | ij_xml_align_text = false 458 | ij_xml_attribute_wrap = normal 459 | ij_xml_block_comment_add_space = false 460 | ij_xml_block_comment_at_first_column = true 461 | ij_xml_keep_blank_lines = 1 462 | ij_xml_keep_indents_on_empty_lines = false 463 | ij_xml_keep_line_breaks = true 464 | ij_xml_keep_line_breaks_in_text = true 465 | ij_xml_keep_whitespaces = false 466 | ij_xml_keep_whitespaces_around_cdata = preserve 467 | ij_xml_keep_whitespaces_inside_cdata = false 468 | ij_xml_line_comment_at_first_column = true 469 | ij_xml_space_after_tag_name = false 470 | ij_xml_space_around_equals_in_attribute = false 471 | ij_xml_space_inside_empty_tag = false 472 | ij_xml_text_wrap = normal 473 | 474 | [{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml}] 475 | indent_size = 4 476 | ij_continuation_indent_size = 4 477 | ij_php_align_assignments = false 478 | ij_php_align_class_constants = false 479 | ij_php_align_group_field_declarations = false 480 | ij_php_align_inline_comments = false 481 | ij_php_align_key_value_pairs = false 482 | ij_php_align_match_arm_bodies = false 483 | ij_php_align_multiline_array_initializer_expression = false 484 | ij_php_align_multiline_binary_operation = false 485 | ij_php_align_multiline_chained_methods = false 486 | ij_php_align_multiline_extends_list = false 487 | ij_php_align_multiline_for = false 488 | ij_php_align_multiline_parameters = false 489 | ij_php_align_multiline_parameters_in_calls = false 490 | ij_php_align_multiline_ternary_operation = false 491 | ij_php_align_named_arguments = false 492 | ij_php_align_phpdoc_comments = false 493 | ij_php_align_phpdoc_param_names = false 494 | ij_php_anonymous_brace_style = end_of_line 495 | ij_php_api_weight = 28 496 | ij_php_array_initializer_new_line_after_left_brace = true 497 | ij_php_array_initializer_right_brace_on_new_line = true 498 | ij_php_array_initializer_wrap = on_every_item 499 | ij_php_assignment_wrap = off 500 | ij_php_attributes_wrap = off 501 | ij_php_author_weight = 28 502 | ij_php_binary_operation_sign_on_next_line = false 503 | ij_php_binary_operation_wrap = off 504 | ij_php_blank_lines_after_class_header = 0 505 | ij_php_blank_lines_after_function = 1 506 | ij_php_blank_lines_after_imports = 1 507 | ij_php_blank_lines_after_opening_tag = 0 508 | ij_php_blank_lines_after_package = 1 509 | ij_php_blank_lines_around_class = 1 510 | ij_php_blank_lines_around_constants = 0 511 | ij_php_blank_lines_around_field = 0 512 | ij_php_blank_lines_around_method = 1 513 | ij_php_blank_lines_before_class_end = 0 514 | ij_php_blank_lines_before_imports = 1 515 | ij_php_blank_lines_before_method_body = 0 516 | ij_php_blank_lines_before_package = 1 517 | ij_php_blank_lines_before_return_statement = 0 518 | ij_php_blank_lines_between_imports = 0 519 | ij_php_block_brace_style = end_of_line 520 | ij_php_call_parameters_new_line_after_left_paren = true 521 | ij_php_call_parameters_right_paren_on_new_line = true 522 | ij_php_call_parameters_wrap = on_every_item 523 | ij_php_catch_on_new_line = false 524 | ij_php_category_weight = 28 525 | ij_php_class_brace_style = next_line 526 | ij_php_comma_after_last_array_element = true 527 | ij_php_concat_spaces = true 528 | ij_php_copyright_weight = 28 529 | ij_php_deprecated_weight = 28 530 | ij_php_do_while_brace_force = always 531 | ij_php_else_if_style = combine 532 | ij_php_else_on_new_line = false 533 | ij_php_example_weight = 28 534 | ij_php_extends_keyword_wrap = off 535 | ij_php_extends_list_wrap = off 536 | ij_php_fields_default_visibility = private 537 | ij_php_filesource_weight = 28 538 | ij_php_finally_on_new_line = false 539 | ij_php_for_brace_force = always 540 | ij_php_for_statement_new_line_after_left_paren = false 541 | ij_php_for_statement_right_paren_on_new_line = false 542 | ij_php_for_statement_wrap = off 543 | ij_php_force_empty_methods_in_one_line = false 544 | ij_php_force_short_declaration_array_style = true 545 | ij_php_getters_setters_naming_style = camel_case 546 | ij_php_getters_setters_order_style = getters_first 547 | ij_php_global_weight = 28 548 | ij_php_group_use_wrap = on_every_item 549 | ij_php_if_brace_force = always 550 | ij_php_if_lparen_on_next_line = false 551 | ij_php_if_rparen_on_next_line = false 552 | ij_php_ignore_weight = 28 553 | ij_php_import_sorting = alphabetic 554 | ij_php_indent_break_from_case = true 555 | ij_php_indent_case_from_switch = true 556 | ij_php_indent_code_in_php_tags = false 557 | ij_php_internal_weight = 28 558 | ij_php_keep_blank_lines_after_lbrace = 2 559 | ij_php_keep_blank_lines_before_right_brace = 2 560 | ij_php_keep_blank_lines_in_code = 2 561 | ij_php_keep_blank_lines_in_declarations = 2 562 | ij_php_keep_control_statement_in_one_line = true 563 | ij_php_keep_first_column_comment = true 564 | ij_php_keep_indents_on_empty_lines = false 565 | ij_php_keep_line_breaks = true 566 | ij_php_keep_rparen_and_lbrace_on_one_line = true 567 | ij_php_keep_simple_classes_in_one_line = false 568 | ij_php_keep_simple_methods_in_one_line = false 569 | ij_php_lambda_brace_style = end_of_line 570 | ij_php_license_weight = 28 571 | ij_php_line_comment_add_space = false 572 | ij_php_line_comment_at_first_column = true 573 | ij_php_link_weight = 28 574 | ij_php_lower_case_boolean_const = true 575 | ij_php_lower_case_keywords = true 576 | ij_php_lower_case_null_const = true 577 | ij_php_method_brace_style = next_line 578 | ij_php_method_call_chain_wrap = off 579 | ij_php_method_parameters_new_line_after_left_paren = false 580 | ij_php_method_parameters_right_paren_on_new_line = false 581 | ij_php_method_parameters_wrap = on_every_item 582 | ij_php_method_weight = 28 583 | ij_php_modifier_list_wrap = false 584 | ij_php_multiline_chained_calls_semicolon_on_new_line = false 585 | ij_php_namespace_brace_style = 1 586 | ij_php_new_line_after_php_opening_tag = true 587 | ij_php_null_type_position = in_the_end 588 | ij_php_package_weight = 28 589 | ij_php_param_weight = 0 590 | ij_php_parameters_attributes_wrap = off 591 | ij_php_parentheses_expression_new_line_after_left_paren = false 592 | ij_php_parentheses_expression_right_paren_on_new_line = false 593 | ij_php_phpdoc_blank_line_before_tags = true 594 | ij_php_phpdoc_blank_lines_around_parameters = false 595 | ij_php_phpdoc_keep_blank_lines = true 596 | ij_php_phpdoc_param_spaces_between_name_and_description = 1 597 | ij_php_phpdoc_param_spaces_between_tag_and_type = 1 598 | ij_php_phpdoc_param_spaces_between_type_and_name = 1 599 | ij_php_phpdoc_use_fqcn = true 600 | ij_php_phpdoc_wrap_long_lines = false 601 | ij_php_place_assignment_sign_on_next_line = false 602 | ij_php_place_parens_for_constructor = 1 603 | ij_php_property_read_weight = 28 604 | ij_php_property_weight = 28 605 | ij_php_property_write_weight = 28 606 | ij_php_return_type_on_new_line = false 607 | ij_php_return_weight = 1 608 | ij_php_see_weight = 28 609 | ij_php_since_weight = 28 610 | ij_php_sort_phpdoc_elements = true 611 | ij_php_space_after_colon = true 612 | ij_php_space_after_colon_in_enum_backed_type = true 613 | ij_php_space_after_colon_in_named_argument = true 614 | ij_php_space_after_colon_in_return_type = true 615 | ij_php_space_after_comma = true 616 | ij_php_space_after_for_semicolon = true 617 | ij_php_space_after_quest = true 618 | ij_php_space_after_type_cast = false 619 | ij_php_space_after_unary_not = false 620 | ij_php_space_before_array_initializer_left_brace = false 621 | ij_php_space_before_catch_keyword = true 622 | ij_php_space_before_catch_left_brace = true 623 | ij_php_space_before_catch_parentheses = true 624 | ij_php_space_before_class_left_brace = true 625 | ij_php_space_before_closure_left_parenthesis = true 626 | ij_php_space_before_colon = true 627 | ij_php_space_before_colon_in_enum_backed_type = false 628 | ij_php_space_before_colon_in_named_argument = false 629 | ij_php_space_before_colon_in_return_type = false 630 | ij_php_space_before_comma = false 631 | ij_php_space_before_do_left_brace = true 632 | ij_php_space_before_else_keyword = true 633 | ij_php_space_before_else_left_brace = true 634 | ij_php_space_before_finally_keyword = true 635 | ij_php_space_before_finally_left_brace = true 636 | ij_php_space_before_for_left_brace = true 637 | ij_php_space_before_for_parentheses = true 638 | ij_php_space_before_for_semicolon = false 639 | ij_php_space_before_if_left_brace = true 640 | ij_php_space_before_if_parentheses = true 641 | ij_php_space_before_method_call_parentheses = false 642 | ij_php_space_before_method_left_brace = true 643 | ij_php_space_before_method_parentheses = false 644 | ij_php_space_before_quest = true 645 | ij_php_space_before_short_closure_left_parenthesis = false 646 | ij_php_space_before_switch_left_brace = true 647 | ij_php_space_before_switch_parentheses = true 648 | ij_php_space_before_try_left_brace = true 649 | ij_php_space_before_unary_not = false 650 | ij_php_space_before_while_keyword = true 651 | ij_php_space_before_while_left_brace = true 652 | ij_php_space_before_while_parentheses = true 653 | ij_php_space_between_ternary_quest_and_colon = false 654 | ij_php_spaces_around_additive_operators = true 655 | ij_php_spaces_around_arrow = false 656 | ij_php_spaces_around_assignment_in_declare = false 657 | ij_php_spaces_around_assignment_operators = true 658 | ij_php_spaces_around_bitwise_operators = true 659 | ij_php_spaces_around_equality_operators = true 660 | ij_php_spaces_around_logical_operators = true 661 | ij_php_spaces_around_multiplicative_operators = true 662 | ij_php_spaces_around_null_coalesce_operator = true 663 | ij_php_spaces_around_pipe_in_union_type = false 664 | ij_php_spaces_around_relational_operators = true 665 | ij_php_spaces_around_shift_operators = true 666 | ij_php_spaces_around_unary_operator = false 667 | ij_php_spaces_around_var_within_brackets = false 668 | ij_php_spaces_within_array_initializer_braces = false 669 | ij_php_spaces_within_brackets = false 670 | ij_php_spaces_within_catch_parentheses = false 671 | ij_php_spaces_within_for_parentheses = false 672 | ij_php_spaces_within_if_parentheses = false 673 | ij_php_spaces_within_method_call_parentheses = false 674 | ij_php_spaces_within_method_parentheses = false 675 | ij_php_spaces_within_parentheses = false 676 | ij_php_spaces_within_short_echo_tags = true 677 | ij_php_spaces_within_switch_parentheses = false 678 | ij_php_spaces_within_while_parentheses = false 679 | ij_php_special_else_if_treatment = false 680 | ij_php_subpackage_weight = 28 681 | ij_php_ternary_operation_signs_on_next_line = false 682 | ij_php_ternary_operation_wrap = off 683 | ij_php_throws_weight = 2 684 | ij_php_todo_weight = 28 685 | ij_php_unknown_tag_weight = 28 686 | ij_php_upper_case_boolean_const = false 687 | ij_php_upper_case_null_const = false 688 | ij_php_uses_weight = 28 689 | ij_php_var_weight = 28 690 | ij_php_variable_naming_style = camel_case 691 | ij_php_version_weight = 28 692 | ij_php_while_brace_force = always 693 | ij_php_while_on_new_line = false 694 | 695 | [{*.scss,*.vue-scss}] 696 | ij_scss_align_closing_brace_with_properties = false 697 | ij_scss_blank_lines_around_nested_selector = 1 698 | ij_scss_blank_lines_between_blocks = 1 699 | ij_scss_block_comment_add_space = false 700 | ij_scss_brace_placement = 0 701 | ij_scss_enforce_quotes_on_format = true 702 | ij_scss_hex_color_long_format = true 703 | ij_scss_hex_color_lower_case = true 704 | ij_scss_hex_color_short_format = false 705 | ij_scss_hex_color_upper_case = false 706 | ij_scss_keep_blank_lines_in_code = 2 707 | ij_scss_keep_indents_on_empty_lines = false 708 | ij_scss_keep_single_line_blocks = false 709 | ij_scss_line_comment_add_space = false 710 | ij_scss_line_comment_at_first_column = false 711 | ij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow 712 | ij_scss_space_after_colon = true 713 | ij_scss_space_before_opening_brace = true 714 | ij_scss_use_double_quotes = false 715 | ij_scss_value_alignment = 0 716 | 717 | [*.vue] 718 | ij_vue_indent_children_of_top_level = template 719 | ij_vue_interpolation_new_line_after_start_delimiter = true 720 | ij_vue_interpolation_new_line_before_end_delimiter = true 721 | ij_vue_interpolation_wrap = off 722 | ij_vue_keep_indents_on_empty_lines = false 723 | ij_vue_spaces_within_interpolation_expressions = true 724 | --------------------------------------------------------------------------------