diff --git a/Classes/Service/ConfigService.php b/Classes/Service/ConfigService.php index f1c5b49..51ea861 100644 --- a/Classes/Service/ConfigService.php +++ b/Classes/Service/ConfigService.php @@ -4,17 +4,22 @@ namespace Kanti\ServerTiming\Service; +use Kanti\ServerTiming\Utility\TimingUtility; + final class ConfigService { + /** @var int */ + private const DEFAULT_STOP_WATCH_LIMIT = 100_000; + public function stopWatchLimit(): int { - return (int)($this->getConfig('stop_watch_limit') ?? 100_000); + return (int)($this->getConfig('stop_watch_limit') ?: self::DEFAULT_STOP_WATCH_LIMIT); } public function tracesSampleRate(): ?float { - $tracesSampleRate = $this->getConfig('sentry_sample_rate'); - return $tracesSampleRate === null ? null : (float)$tracesSampleRate; + $tracesSampleRate = $this->getConfig(TimingUtility::IS_CLI ? 'sentry_cli_sample_rate' : 'sentry_sample_rate'); + return $tracesSampleRate === '' ? null : (float)$tracesSampleRate; } public function enableTracing(): ?bool @@ -23,8 +28,8 @@ public function enableTracing(): ?bool return $tracesSampleRate === null ? null : (bool)$tracesSampleRate; } - private function getConfig(string $path): ?string + private function getConfig(string $path): string { - return $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['server_timing'][$path] ?? null; + return (string)($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['server_timing'][$path] ?? ''); } } diff --git a/Classes/Service/RegisterShutdownFunction/RegisterShutdownFunction.php b/Classes/Service/RegisterShutdownFunction/RegisterShutdownFunction.php new file mode 100644 index 0000000..6751369 --- /dev/null +++ b/Classes/Service/RegisterShutdownFunction/RegisterShutdownFunction.php @@ -0,0 +1,13 @@ +callCount++; + } +} diff --git a/Classes/Utility/TimingUtility.php b/Classes/Utility/TimingUtility.php index 2f46fd1..c679db9 100644 --- a/Classes/Utility/TimingUtility.php +++ b/Classes/Utility/TimingUtility.php @@ -4,22 +4,21 @@ namespace Kanti\ServerTiming\Utility; -use Closure; use Exception; use Kanti\ServerTiming\Dto\ScriptResult; use Kanti\ServerTiming\Dto\StopWatch; +use Kanti\ServerTiming\Service\RegisterShutdownFunction\RegisterShutdownFunctionInterface; use Kanti\ServerTiming\Service\SentryService; use Kanti\ServerTiming\Service\ConfigService; use Psr\Http\Message\ResponseInterface; use SplObjectStorage; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -final class TimingUtility +final class TimingUtility implements SingletonInterface { - private static ?TimingUtility $instance = null; - private bool $registered = false; /** @var bool */ @@ -27,20 +26,13 @@ final class TimingUtility private bool $alreadyShutdown = false; - private readonly Closure $registerShutdownFunction; - - private readonly ConfigService $configService; - - public function __construct(Closure $registerShutdownFunction = null, ConfigService $configService = null) + public function __construct(private readonly RegisterShutdownFunctionInterface $registerShutdownFunction, private readonly ConfigService $configService) { - $this->registerShutdownFunction = $registerShutdownFunction ?? register_shutdown_function(...); - $this->configService = $configService ?? new ConfigService(); } - public static function getInstance(Closure $registerShutdownFunction = null): TimingUtility + public static function getInstance(): TimingUtility { - // to not use GeneralUtility::makeInstance( as this is maybe called to early in the stack) - return self::$instance ??= new self($registerShutdownFunction); + return GeneralUtility::makeInstance(TimingUtility::class); } /** @var StopWatch[] */ @@ -115,8 +107,7 @@ public function stopWatchInternal(string $key, string $info = ''): StopWatch } if (!$this->registered) { - $x = $this->registerShutdownFunction; - $x(fn(): ?ResponseInterface => $this->shutdown(ScriptResult::fromShutdown())); + $this->registerShutdownFunction->register(fn(): ?ResponseInterface => $this->shutdown(ScriptResult::fromShutdown())); $this->registered = true; } } diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index f7914d6..8f666ca 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -9,6 +9,9 @@ services: # SqlLogging can be removed from exclude if only TYPO3 12 and above are required exclude: '../Classes/{Dto,SqlLogging}/*' + Kanti\ServerTiming\Service\RegisterShutdownFunction\RegisterShutdownFunctionInterface: + class: Kanti\ServerTiming\Service\RegisterShutdownFunction\RegisterShutdownFunction + Kanti\ServerTiming\EventListener\ConsoleCommandEventListener: tags: - diff --git a/Tests/TimingUtilityTest.php b/Tests/TimingUtilityTest.php index 03b918b..5afb2c4 100644 --- a/Tests/TimingUtilityTest.php +++ b/Tests/TimingUtilityTest.php @@ -6,6 +6,7 @@ use Generator; use Kanti\ServerTiming\Service\ConfigService; +use Kanti\ServerTiming\Service\RegisterShutdownFunction\RegisterShutdownFunctionNoop; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; @@ -13,16 +14,23 @@ use Kanti\ServerTiming\Utility\TimingUtility; use PHPUnit\Framework\TestCase; use ReflectionClass; +use TYPO3\CMS\Core\Utility\GeneralUtility; #[CoversClass(TimingUtility::class)] #[CoversClass(StopWatch::class)] #[CoversClass(ConfigService::class)] +#[CoversClass(RegisterShutdownFunctionNoop::class)] final class TimingUtilityTest extends TestCase { protected function setUp(): void { - // do not register a real shutdown function: - TimingUtility::getInstance(static fn(): int => 0); + GeneralUtility::setSingletonInstance(TimingUtility::class, $this->getTestInstance()); + } + + protected function tearDown(): void + { + unset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['server_timing']); + GeneralUtility::resetSingletonInstances([]); } #[Test] @@ -75,14 +83,14 @@ public function stopWatchStopIfNot(): void #[Test] public function stopWatchInternal(): void { - (new TimingUtility(static fn(): int => 0))->stopWatchInternal('test'); + TimingUtility::getInstance()->stopWatchInternal('test'); self::assertTrue(true, 'isCallable'); } #[Test] public function stopWatchFirstIsAlwaysPhp(): void { - $timingUtility = new TimingUtility(static fn(): int => 0); + $timingUtility = $this->getTestInstance(); $timingUtility->stopWatchInternal('test'); $watches = $timingUtility->getStopWatches(); @@ -91,19 +99,31 @@ public function stopWatchFirstIsAlwaysPhp(): void self::assertSame('test', $watches[1]->key); } + #[Test] + public function stopWatchLimit(): void + { + $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['server_timing']['stop_watch_limit'] = 3; + $timingUtility = $this->getTestInstance(); + $timingUtility->stopWatchInternal('test'); + $timingUtility->stopWatchInternal('test'); + $timingUtility->stopWatchInternal('test'); + + $watches = $timingUtility->getStopWatches(); + self::assertCount(3, $watches); + self::assertSame('php', $watches[0]->key); + self::assertSame('test', $watches[1]->key); + self::assertSame('test', $watches[2]->key); + } + #[Test] public function didRegisterShutdownFunctionOnce(): void { - $called = 0; - $timingUtility = new TimingUtility(static function ($callback) use (&$called): void { - self::assertIsCallable($callback); - $called++; - }); + $timingUtility = new TimingUtility($registerShutdownFunction = new RegisterShutdownFunctionNoop(), new ConfigService()); $timingUtility->stopWatchInternal('test'); $timingUtility->stopWatchInternal('test'); $timingUtility->stopWatchInternal('test'); $timingUtility->stopWatchInternal('test'); - self::assertSame(1, $called); + self::assertSame(1, $registerShutdownFunction->callCount); } #[Test] @@ -127,7 +147,7 @@ public function timingString(string $expected, array $args): void $reflection = new ReflectionClass(TimingUtility::class); $reflectionMethod = $reflection->getMethod('timingString'); - $result = $reflectionMethod->invoke(new TimingUtility(static fn(): int => 0), ...$args); + $result = $reflectionMethod->invoke($this->getTestInstance(), ...$args); self::assertSame($expected, $result); } @@ -163,7 +183,7 @@ public function combineIfToMuch(array $expected, array $initalStopWatches): void $initalStopWatches = array_map(static fn(StopWatch $el): StopWatch => clone $el, $initalStopWatches); $reflectionMethod = $reflection->getMethod('combineIfToMuch'); - $result = $reflectionMethod->invoke(new TimingUtility(static fn(): int => 0), $initalStopWatches); + $result = $reflectionMethod->invoke($this->getTestInstance(), $initalStopWatches); self::assertEqualsWithDelta($expected, $result, 0.00001); } @@ -239,7 +259,7 @@ public function shouldTrack(): void $reflection = new ReflectionClass(TimingUtility::class); $isAlreadyShutdown = $reflection->getProperty('alreadyShutdown'); - $timingUtility = new TimingUtility(static fn(): int => 0); + $timingUtility = $this->getTestInstance(); $isAlreadyShutdown->setValue($timingUtility, false); self::assertTrue($timingUtility->shouldTrack()); @@ -247,4 +267,9 @@ public function shouldTrack(): void $isAlreadyShutdown->setValue($timingUtility, true); self::assertFalse($timingUtility->shouldTrack()); } + + private function getTestInstance(): TimingUtility + { + return new TimingUtility(new RegisterShutdownFunctionNoop(), new ConfigService()); + } } diff --git a/composer.json b/composer.json index c10aadc..b473349 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "infection/infection": "^0.26.13", "phpstan/extension-installer": "^1.1", "phpunit/phpunit": "^10", - "pluswerk/grumphp-config": "^6.7", + "pluswerk/grumphp-config": "^6.8.0", "saschaegerer/phpstan-typo3": "^1.1", "sentry/sdk": "^3.5", "spatie/phpunit-watcher": "^1.23",