├── .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 |
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 | 
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 | 
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 |
--------------------------------------------------------------------------------