diff --git a/psalm.xml b/psalm.xml index 2219c8dac..91b98d3e8 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ commands[$event::class] = [ - 'name' => $command->getName(), + 'name' => $command?->getName() ?? $event->getInput()->getFirstArgument() ?? '', 'command' => $command, 'input' => $this->castInputToString($event->getInput()), 'output' => $this->fetchOutput($event->getOutput()), @@ -68,13 +68,14 @@ public function collect(ConsoleEvent|ConsoleErrorEvent|ConsoleTerminateEvent $ev return; } + $definition = $command?->getDefinition(); $this->commands[$event::class] = [ - 'name' => $command->getName(), + 'name' => $command?->getName() ?? $event->getInput()->getFirstArgument() ?? '', 'command' => $command, 'input' => $this->castInputToString($event->getInput()), 'output' => $this->fetchOutput($event->getOutput()), - 'arguments' => $command->getDefinition()->getArguments(), - 'options' => $command->getDefinition()->getOptions(), + 'arguments' => $definition?->getArguments() ?? [], + 'options' => $definition?->getOptions() ?? [], ]; } diff --git a/src/Collector/ContainerInterfaceProxy.php b/src/Collector/ContainerInterfaceProxy.php index 07fe04c6a..960169bed 100644 --- a/src/Collector/ContainerInterfaceProxy.php +++ b/src/Collector/ContainerInterfaceProxy.php @@ -14,7 +14,7 @@ use function is_object; use function is_string; -class ContainerInterfaceProxy implements ContainerInterface +final class ContainerInterfaceProxy implements ContainerInterface { use ProxyLogTrait; use ProxyTrait; @@ -43,15 +43,12 @@ public function withDecoratedServices(array $decoratedServices): self return $new; } - /** - * @psalm-suppress InvalidCatch - */ - public function get($id) + public function get($id): mixed { $this->resetCurrentError(); $timeStart = microtime(true); + $instance = null; try { - $instance = null; $instance = $this->getInstance($id); } catch (ContainerExceptionInterface $e) { $this->repeatError($e); @@ -61,7 +58,10 @@ public function get($id) if ( is_object($instance) - && (($proxy = $this->getServiceProxyCache($id)) || ($proxy = $this->getServiceProxy($id, $instance))) + && ( + ($proxy = $this->getServiceProxyCache($id)) || + ($proxy = $this->getServiceProxy($id, $instance)) + ) ) { $this->setServiceProxyCache($id, $proxy); return $proxy; @@ -70,7 +70,10 @@ public function get($id) return $instance; } - private function getInstance(string $id) + /** + * @throws ContainerExceptionInterface + */ + private function getInstance(string $id): mixed { if ($id === ContainerInterface::class) { return $this; @@ -105,7 +108,11 @@ private function getServiceProxy(string $service, object $instance): ?object } if ($this->config->hasDecoratedServiceArrayConfigWithStringKeys($service)) { - return $this->getCommonMethodProxy($service, $instance, $this->config->getDecoratedServiceConfig($service)); + return $this->getCommonMethodProxy( + interface_exists($service) || class_exists($service) ? $service : $instance::class, + $instance, + $this->config->getDecoratedServiceConfig($service) + ); } if ($this->config->hasDecoratedServiceArrayConfig($service)) { @@ -124,6 +131,9 @@ private function getServiceProxyFromCallable(callable $callback): ?object return $callback($this); } + /** + * @psalm-param class-string $service + */ private function getCommonMethodProxy(string $service, object $instance, array $callbacks): ?object { $methods = []; @@ -159,6 +169,9 @@ private function getServiceProxyFromArray(object $instance, array $params): ?obj } } + /** + * @psalm-param class-string $service + */ private function getCommonServiceProxy(string $service, object $instance): object { return $this->proxyManager->createObjectProxy( diff --git a/src/Collector/EventDispatcherInterfaceProxy.php b/src/Collector/EventDispatcherInterfaceProxy.php index 4d082129e..90861d8d2 100644 --- a/src/Collector/EventDispatcherInterfaceProxy.php +++ b/src/Collector/EventDispatcherInterfaceProxy.php @@ -16,7 +16,8 @@ public function __construct( public function dispatch(object $event): object { - [$callStack] = debug_backtrace(); + /** @psalm-var array{file: string, line: int} $callStack */ + $callStack = debug_backtrace()[0]; $this->collector->collect($event, $callStack['file'] . ':' . $callStack['line']); diff --git a/src/Collector/HttpClientCollector.php b/src/Collector/HttpClientCollector.php index 1aef6d593..1080d7a83 100644 --- a/src/Collector/HttpClientCollector.php +++ b/src/Collector/HttpClientCollector.php @@ -16,8 +16,8 @@ final class HttpClientCollector implements SummaryCollectorInterface /** * @psalm-var arrayisActive()) { return; diff --git a/src/Collector/HttpClientInterfaceProxy.php b/src/Collector/HttpClientInterfaceProxy.php index f791c1a47..a7c588f70 100644 --- a/src/Collector/HttpClientInterfaceProxy.php +++ b/src/Collector/HttpClientInterfaceProxy.php @@ -16,7 +16,8 @@ public function __construct(private ClientInterface $decorated, private HttpClie public function sendRequest(RequestInterface $request): ResponseInterface { - [$callStack] = debug_backtrace(); + /** @psalm-var array{file: string, line: int} $callStack */ + $callStack = debug_backtrace()[0]; $uniqueId = random_bytes(36); $startTime = microtime(true); diff --git a/src/Collector/LoggerInterfaceProxy.php b/src/Collector/LoggerInterfaceProxy.php index c23a081bf..0575b9b34 100644 --- a/src/Collector/LoggerInterfaceProxy.php +++ b/src/Collector/LoggerInterfaceProxy.php @@ -16,7 +16,7 @@ public function __construct(private LoggerInterface $logger, private LogCollecto public function emergency(string|Stringable $message, array $context = []): void { - [$callStack] = debug_backtrace(); + $callStack = $this->getCallStack(); $this->collector->collect( LogLevel::EMERGENCY, @@ -29,7 +29,7 @@ public function emergency(string|Stringable $message, array $context = []): void public function alert(string|Stringable $message, array $context = []): void { - [$callStack] = debug_backtrace(); + $callStack = $this->getCallStack(); $this->collector->collect(LogLevel::ALERT, $message, $context, $callStack['file'] . ':' . $callStack['line']); $this->logger->alert($message, $context); @@ -37,7 +37,7 @@ public function alert(string|Stringable $message, array $context = []): void public function critical(string|Stringable $message, array $context = []): void { - [$callStack] = debug_backtrace(); + $callStack = $this->getCallStack(); $this->collector->collect( LogLevel::CRITICAL, @@ -50,7 +50,7 @@ public function critical(string|Stringable $message, array $context = []): void public function error(string|Stringable $message, array $context = []): void { - [$callStack] = debug_backtrace(); + $callStack = $this->getCallStack(); $this->collector->collect(LogLevel::ERROR, $message, $context, $callStack['file'] . ':' . $callStack['line']); $this->logger->error($message, $context); @@ -58,7 +58,7 @@ public function error(string|Stringable $message, array $context = []): void public function warning(string|Stringable $message, array $context = []): void { - [$callStack] = debug_backtrace(); + $callStack = $this->getCallStack(); $this->collector->collect(LogLevel::WARNING, $message, $context, $callStack['file'] . ':' . $callStack['line']); $this->logger->warning($message, $context); @@ -66,7 +66,7 @@ public function warning(string|Stringable $message, array $context = []): void public function notice(string|Stringable $message, array $context = []): void { - [$callStack] = debug_backtrace(); + $callStack = $this->getCallStack(); $this->collector->collect(LogLevel::NOTICE, $message, $context, $callStack['file'] . ':' . $callStack['line']); $this->logger->notice($message, $context); @@ -74,7 +74,7 @@ public function notice(string|Stringable $message, array $context = []): void public function info(string|Stringable $message, array $context = []): void { - [$callStack] = debug_backtrace(); + $callStack = $this->getCallStack(); $this->collector->collect(LogLevel::INFO, $message, $context, $callStack['file'] . ':' . $callStack['line']); $this->logger->info($message, $context); @@ -82,7 +82,7 @@ public function info(string|Stringable $message, array $context = []): void public function debug(string|Stringable $message, array $context = []): void { - [$callStack] = debug_backtrace(); + $callStack = $this->getCallStack(); $this->collector->collect(LogLevel::DEBUG, $message, $context, $callStack['file'] . ':' . $callStack['line']); $this->logger->debug($message, $context); @@ -90,9 +90,18 @@ public function debug(string|Stringable $message, array $context = []): void public function log(mixed $level, string|Stringable $message, array $context = []): void { - [$callStack] = debug_backtrace(); + $callStack = $this->getCallStack(); $this->collector->collect($level, $message, $context, $callStack['file'] . ':' . $callStack['line']); $this->logger->log($level, $message, $context); } + + /** + * @psalm-return array{file: string, line: int} + */ + private function getCallStack(): array + { + /** @psalm-var array{file: string, line: int} */ + return debug_backtrace()[1]; + } } diff --git a/src/Collector/Stream/HttpStreamProxy.php b/src/Collector/Stream/HttpStreamProxy.php index cd85ffac1..a178a0383 100644 --- a/src/Collector/Stream/HttpStreamProxy.php +++ b/src/Collector/Stream/HttpStreamProxy.php @@ -106,6 +106,9 @@ public function stream_open(string $path, string $mode, int $options, ?string &$ public function stream_read(int $count): string|false { if (!$this->ignored) { + /** + * @psalm-suppress PossiblyNullArgument + */ $metadata = stream_get_meta_data($this->decorated->stream); $context = $this->decorated->context === null ? null diff --git a/src/Collector/VarDumperHandlerInterfaceProxy.php b/src/Collector/VarDumperHandlerInterfaceProxy.php index c76f2cd04..c8c7ae644 100644 --- a/src/Collector/VarDumperHandlerInterfaceProxy.php +++ b/src/Collector/VarDumperHandlerInterfaceProxy.php @@ -32,6 +32,7 @@ public function handle(mixed $variable, int $depth, bool $highlight = false): vo $callStack = $value; break; } + /** @psalm-var array{file: string, line: int} $callStack */ $this->collector->collect( $variable, diff --git a/src/Dumper.php b/src/Dumper.php index b2d0f3d72..423c43477 100644 --- a/src/Dumper.php +++ b/src/Dumper.php @@ -38,9 +38,9 @@ public static function create(mixed $variable, array $excludedClasses = []): sel * @param int $depth Maximum depth that the dumper should go into the variable. * @param bool $format Whatever to format exported code. * - * @return bool|string JSON string. + * @return string JSON string. */ - public function asJson(int $depth = 50, bool $format = false): string|bool + public function asJson(int $depth = 50, bool $format = false): string { $this->buildObjectsCache($this->variable, $depth); return $this->asJsonInternal($this->variable, $format, $depth, 0, false); @@ -52,9 +52,9 @@ public function asJson(int $depth = 50, bool $format = false): string|bool * @param int $depth Maximum depth that the dumper should go into the variable. * @param bool $prettyPrint Whatever to format exported code. * - * @return bool|string JSON string containing summary. + * @return string JSON string containing summary. */ - public function asJsonObjectsMap(int $depth = 50, bool $prettyPrint = false): string|bool + public function asJsonObjectsMap(int $depth = 50, bool $prettyPrint = false): string { $this->buildObjectsCache($this->variable, $depth); return $this->asJsonInternal($this->objects, $prettyPrint, $depth, 1, true); @@ -94,7 +94,7 @@ private function asJsonInternal( int $depth, int $objectCollapseLevel, bool $inlineObject, - ): string|bool { + ): string { $options = JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE; if ($format) { diff --git a/src/Helper/StreamWrapper/StreamWrapper.php b/src/Helper/StreamWrapper/StreamWrapper.php index 859c9b4a5..7bafcc087 100644 --- a/src/Helper/StreamWrapper/StreamWrapper.php +++ b/src/Helper/StreamWrapper/StreamWrapper.php @@ -31,6 +31,9 @@ final class StreamWrapper implements StreamWrapperInterface public function dir_closedir(): bool { + /** + * @psalm-suppress PossiblyNullArgument + */ closedir($this->stream); /** @@ -42,12 +45,19 @@ public function dir_closedir(): bool public function dir_opendir(string $path, int $options): bool { $this->filename = $path; + + /** + * @psalm-suppress PossiblyNullArgument + */ $this->stream = opendir($path, $this->context); return is_resource($this->stream); } public function dir_readdir(): false|string { + /** + * @psalm-suppress PossiblyNullArgument + */ return readdir($this->stream); } @@ -69,26 +79,39 @@ public function dir_rewinddir(): bool public function mkdir(string $path, int $mode, int $options): bool { $this->filename = $path; + + /** + * @psalm-suppress PossiblyNullArgument + */ return mkdir($path, $mode, ($options & STREAM_MKDIR_RECURSIVE) === STREAM_MKDIR_RECURSIVE, $this->context); } public function rename(string $path_from, string $path_to): bool { + /** + * @psalm-suppress PossiblyNullArgument + */ return rename($path_from, $path_to, $this->context); } public function rmdir(string $path, int $options): bool { + /** + * @psalm-suppress PossiblyNullArgument + */ return rmdir($path, $this->context); } public function stream_cast(int $castAs) { - //???? + // ??? } public function stream_eof(): bool { + /** + * @psalm-suppress PossiblyNullArgument + */ return feof($this->stream); } @@ -121,16 +144,25 @@ public function stream_open(string $path, string $mode, int $options, ?string &$ public function stream_read(int $count): string|false { + /** + * @psalm-suppress PossiblyNullArgument + */ return fread($this->stream, $count); } public function stream_seek(int $offset, int $whence = SEEK_SET): bool { + /** + * @psalm-suppress PossiblyNullArgument + */ return fseek($this->stream, $offset, $whence) !== -1; } public function stream_set_option(int $option, int $arg1, int $arg2): bool { + /** + * @psalm-suppress PossiblyNullArgument + */ return match ($option) { STREAM_OPTION_BLOCKING => stream_set_blocking($this->stream, $arg1 === STREAM_OPTION_BLOCKING), STREAM_OPTION_READ_TIMEOUT => stream_set_timeout($this->stream, $arg1, $arg2), @@ -141,16 +173,25 @@ public function stream_set_option(int $option, int $arg1, int $arg2): bool public function stream_stat(): array|false { + /** + * @psalm-suppress PossiblyNullArgument + */ return fstat($this->stream); } public function stream_tell(): int { + /** + * @psalm-suppress PossiblyNullArgument + */ return ftell($this->stream); } public function stream_write(string $data): int { + /** + * @psalm-suppress PossiblyNullArgument + */ return fwrite($this->stream, $data); } @@ -184,6 +225,9 @@ public function stream_metadata(string $path, int $option, mixed $value): bool public function stream_flush(): bool { + /** + * @psalm-suppress PossiblyNullArgument + */ return fflush($this->stream); } @@ -203,16 +247,26 @@ public function stream_lock(int $operation): bool if ($operation === 0) { $operation = LOCK_EX; } + + /** + * @psalm-suppress PossiblyNullArgument + */ return flock($this->stream, $operation); } public function stream_truncate(int $new_size): bool { + /** + * @psalm-suppress PossiblyNullArgument + */ return ftruncate($this->stream, $new_size); } public function unlink(string $path): bool { + /** + * @psalm-suppress PossiblyNullArgument + */ return unlink($path, $this->context); } } diff --git a/tests/Unit/Collector/ContainerInterfaceProxyTest.php b/tests/Unit/Collector/ContainerInterfaceProxyTest.php index 4f2da15d0..083f59c70 100644 --- a/tests/Unit/Collector/ContainerInterfaceProxyTest.php +++ b/tests/Unit/Collector/ContainerInterfaceProxyTest.php @@ -33,7 +33,7 @@ use Yiisoft\Yii\Debug\Tests\Support\Stub\Interface1; use Yiisoft\Yii\Debug\Tests\Support\Stub\Interface2; -class ContainerInterfaceProxyTest extends TestCase +final class ContainerInterfaceProxyTest extends TestCase { private string $path = 'tests/container-proxy'; @@ -323,7 +323,26 @@ public function test1(): void $implementation = $containerProxy->get(Interface2::class); $this->assertNotNull($implementation); $this->assertInstanceOf(Interface2::class, $implementation); - $this->assertEquals('from tests', $implementation->getName()); + $this->assertSame('from tests', $implementation->getName()); + } + + public function test2(): void + { + $config = $this->createConfig(ContainerInterfaceProxy::LOG_ERROR); + $config = $config->withDecoratedServices([ + 'test-interface' => ['getName' => fn() => 'from tests'], + ]); + $serviceCollector = $config->getCollector(); + $serviceCollector->startup(); + $container = $this->createContainer([ + 'test-interface' => Implementation2::class, + ]); + $containerProxy = new ContainerInterfaceProxy($container, $config); + + $implementation = $containerProxy->get('test-interface'); + $this->assertNotNull($implementation); + $this->assertInstanceOf(Interface2::class, $implementation); + $this->assertSame('from tests', $implementation->getName()); } private function createConfig(int $logLevel = ContainerInterfaceProxy::LOG_ARGUMENTS): ContainerProxyConfig diff --git a/tests/Unit/Collector/LoggerProxyTest.php b/tests/Unit/Collector/LoggerInterfaceProxyTest.php similarity index 85% rename from tests/Unit/Collector/LoggerProxyTest.php rename to tests/Unit/Collector/LoggerInterfaceProxyTest.php index ba9c2f660..76763b92a 100644 --- a/tests/Unit/Collector/LoggerProxyTest.php +++ b/tests/Unit/Collector/LoggerInterfaceProxyTest.php @@ -4,17 +4,16 @@ namespace Yiisoft\Yii\Debug\Tests\Unit\Collector; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use Yiisoft\Yii\Debug\Collector\LogCollector; use Yiisoft\Yii\Debug\Collector\LoggerInterfaceProxy; -final class LoggerProxyTest extends TestCase +final class LoggerInterfaceProxyTest extends TestCase { - /** - * @dataProvider logMethodsProvider() - */ + #[DataProvider('logMethodsProvider')] public function testLogMethods(string $method, string $level, string $message, array $context): void { $logger = $this->createMock(LoggerInterface::class); @@ -22,15 +21,13 @@ public function testLogMethods(string $method, string $level, string $message, a $collector ->expects($this->once()) ->method('collect') - ->with($level, $message, $context); + ->with($level, $message, $context, __FILE__ . ':27'); $proxy = new LoggerInterfaceProxy($logger, $collector); $proxy->$method($message, $context); } - /** - * @dataProvider logMethodsProvider() - */ + #[DataProvider('logMethodsProvider')] public function testMethodLog($method, string $level, string $message, array $context): void { $logger = $this->createMock(LoggerInterface::class); @@ -38,7 +35,7 @@ public function testMethodLog($method, string $level, string $message, array $co $collector ->expects($this->once()) ->method('collect') - ->with($level, $message, $context); + ->with($level, $message, $context, __FILE__ . ':41'); $proxy = new LoggerInterfaceProxy($logger, $collector); $proxy->log($level, $message, $context);