diff --git a/composer-require-checker.json b/composer-require-checker.json new file mode 100644 index 00000000..96393e83 --- /dev/null +++ b/composer-require-checker.json @@ -0,0 +1,5 @@ +{ + "symbol-whitelist": [ + "opcache_invalidate" + ] +} diff --git a/config/common.php b/config/common.php index 39cedfa9..2607db8b 100644 --- a/config/common.php +++ b/config/common.php @@ -2,12 +2,16 @@ declare(strict_types=1); +use Composer\Autoload\ClassLoader; use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Yiisoft\Aliases\Aliases; +use Yiisoft\VarDumper\ClosureExporter; +use Yiisoft\VarDumper\UseStatementParser; +use Yiisoft\Yii\Debug\Collector\ContainerProxyConfig; +use Yiisoft\Yii\Debug\Collector\FilesystemStreamCollector; use Yiisoft\Yii\Debug\Collector\ServiceCollector; use Yiisoft\Yii\Debug\DebuggerIdGenerator; -use Yiisoft\Yii\Debug\Collector\ContainerProxyConfig; use Yiisoft\Yii\Debug\Storage\FileStorage; use Yiisoft\Yii\Debug\Storage\StorageInterface; @@ -23,13 +27,13 @@ $excludedClasses = $params['dumper.excludedClasses']; $fileStorage = new FileStorage($params['path'], $debuggerIdGenerator, $aliases, $excludedClasses); if (isset($params['historySize'])) { - $fileStorage->setHistorySize((int)$params['historySize']); + $fileStorage->setHistorySize((int) $params['historySize']); } return $fileStorage; }, ]; -if (!(bool)($params['yiisoft/yii-debug']['enabled'] ?? false)) { +if (!(bool) ($params['yiisoft/yii-debug']['enabled'] ?? false)) { return $common; } @@ -38,8 +42,8 @@ $params = $params['yiisoft/yii-debug']; $collector = $container->get(ServiceCollector::class); $dispatcher = $container->get(EventDispatcherInterface::class); - $debuggerEnabled = (bool)($params['enabled'] ?? false); - $trackedServices = (array)($params['trackedServices'] ?? []); + $debuggerEnabled = (bool) ($params['enabled'] ?? false); + $trackedServices = (array) ($params['trackedServices'] ?? []); $path = $container->get(Aliases::class)->get('@runtime/cache/container-proxy'); $logLevel = $params['logLevel'] ?? 0; return new ContainerProxyConfig( @@ -51,4 +55,15 @@ $logLevel ); }, + FilesystemStreamCollector::class => [ + '__construct()' => [ + 'ignoredPathPatterns' => [], + 'ignoredClasses' => [ + ClosureExporter::class, + UseStatementParser::class, + FileStorage::class, + ClassLoader::class, + ], + ], + ], ], $common); diff --git a/config/params.php b/config/params.php index a4500f4c..4685330c 100644 --- a/config/params.php +++ b/config/params.php @@ -18,8 +18,10 @@ use Yiisoft\Yii\Debug\Collector\ContainerInterfaceProxy; use Yiisoft\Yii\Debug\Collector\EventCollector; use Yiisoft\Yii\Debug\Collector\EventDispatcherInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\FilesystemStreamCollector; use Yiisoft\Yii\Debug\Collector\HttpClientCollector; use Yiisoft\Yii\Debug\Collector\HttpClientInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\HttpStreamCollector; use Yiisoft\Yii\Debug\Collector\LogCollector; use Yiisoft\Yii\Debug\Collector\LoggerInterfaceProxy; use Yiisoft\Yii\Debug\Collector\MiddlewareCollector; @@ -52,6 +54,8 @@ ValidatorCollector::class, QueueCollector::class, HttpClientCollector::class, + FilesystemStreamCollector::class, + HttpStreamCollector::class, ], 'collectors.web' => [ WebAppInfoCollector::class, diff --git a/src/Collector/FilesystemStreamCollector.php b/src/Collector/FilesystemStreamCollector.php new file mode 100644 index 00000000..5ea4bc10 --- /dev/null +++ b/src/Collector/FilesystemStreamCollector.php @@ -0,0 +1,81 @@ +operations); + } + + public function startup(): void + { + $this->isActive = true; + FilesystemStreamProxy::register(); + FilesystemStreamProxy::$collector = $this; + FilesystemStreamProxy::$ignoredPathPatterns = $this->ignoredPathPatterns; + FilesystemStreamProxy::$ignoredClasses = $this->ignoredClasses; + } + + public function shutdown(): void + { + FilesystemStreamProxy::unregister(); + FilesystemStreamProxy::$collector = null; + FilesystemStreamProxy::$ignoredPathPatterns = []; + FilesystemStreamProxy::$ignoredClasses = []; + + $this->reset(); + $this->isActive = false; + } + + public function collect(string $operation, string $path, array $args): void + { + if (!$this->isActive()) { + return; + } + + $this->operations[$operation][] = [ + 'path' => $path, + 'args' => $args, + ]; + } + + public function getIndexData(): array + { + return [ + 'fs_stream' => array_merge( + ...array_map( + fn (string $operation) => [$operation => count($this->operations[$operation])], + array_keys($this->operations) + ) + ), + ]; + } + + private function reset(): void + { + $this->operations = []; + } +} diff --git a/src/Collector/FilesystemStreamProxy.php b/src/Collector/FilesystemStreamProxy.php new file mode 100644 index 00000000..73548168 --- /dev/null +++ b/src/Collector/FilesystemStreamProxy.php @@ -0,0 +1,279 @@ +decorated = new StreamWrapper(); + $this->decorated->context = $this->context; + } + + public function __call(string $name, array $arguments) + { + try { + self::unregister(); + return $this->decorated->{$name}(...$arguments); + } finally { + self::register(); + } + } + + public function __destruct() + { + foreach ($this->operations as $name => $operation) { + self::$collector->collect( + operation: $name, + path: $operation['path'], + args: $operation['args'], + ); + } + } + + public function __get(string $name) + { + return $this->decorated->{$name}; + } + + public static function register(): void + { + if (self::$registered) { + return; + } + /** + * It's important to trigger autoloader before unregistering the file stream handler + */ + class_exists(StreamWrapper::class); + stream_wrapper_unregister('file'); + stream_wrapper_register('file', self::class, STREAM_IS_URL); + self::$registered = true; + } + + public static function unregister(): void + { + if (!self::$registered) { + return; + } + @stream_wrapper_restore('file'); + self::$registered = false; + } + + /** + * TODO: optimise the check. Maybe a hashmap? + */ + private function setIgnored(): void + { + $backtrace = debug_backtrace(); + /** + * 0 – Called method + * 1 – Proxy + * 2 – Real using place / Composer\ClassLoader include function + * 3 – Whatever / Composer\ClassLoader + */ + if (isset($backtrace[3]['class']) && in_array($backtrace[3]['class'], self::$ignoredClasses, true)) { + $this->ignored = true; + return; + } + + if (!isset($backtrace[2])) { + return; + } + $path = $backtrace[2]['file']; + + $result = false; + foreach (self::$ignoredPathPatterns as $ignoredPathPattern) { + if (preg_match($ignoredPathPattern, $path) > 0) { + $result = true; + break; + } + } + $this->ignored = $result; + } + + public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool + { + $this->setIgnored(); + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_read(int $count): string|false + { + if (!$this->ignored) { + $this->operations['read'] = [ + 'path' => $this->decorated->filename, + 'args' => [], + ]; + } + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_set_option(int $option, int $arg1, ?int $arg2): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_tell(): int + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_eof(): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_seek(int $offset, int $whence = SEEK_SET): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_cast(int $castAs) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_stat(): array|false + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function dir_closedir(): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function dir_opendir(string $path, int $options): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function dir_readdir(): false|string + { + if (!$this->ignored) { + $this->operations['readdir'] = [ + 'path' => $this->decorated->filename, + 'args' => [], + ]; + } + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function dir_rewinddir(): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function mkdir(string $path, int $mode, int $options): bool + { + if (!$this->ignored) { + $this->operations[__FUNCTION__] = [ + 'path' => $path, + 'args' => [ + 'mode' => $mode, + 'options' => $options, + ], + ]; + } + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function rename(string $path_from, string $path_to): bool + { + if (!$this->ignored) { + $this->operations[__FUNCTION__] = [ + 'path' => $path_from, + 'args' => [ + 'path_to' => $path_to, + ], + ]; + } + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function rmdir(string $path, int $options): bool + { + if (!$this->ignored) { + $this->operations[__FUNCTION__] = [ + 'path' => $path, + 'args' => [ + 'options' => $options, + ], + ]; + } + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_close(): void + { + $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_flush(): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_lock(int $operation): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_metadata(string $path, int $option, mixed $value): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_truncate(int $new_size): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_write(string $data): int + { + if (!$this->ignored) { + $this->operations['write'] = [ + 'path' => $this->decorated->filename, + 'args' => [], + ]; + } + + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function unlink(string $path): bool + { + if (!$this->ignored) { + $this->operations[__FUNCTION__] = [ + 'path' => $path, + 'args' => [], + ]; + } + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function url_stat(string $path, int $flags): array|false + { + return $this->__call(__FUNCTION__, func_get_args()); + } +} diff --git a/src/Collector/HttpClientCollector.php b/src/Collector/HttpClientCollector.php index db418c29..1295cfed 100644 --- a/src/Collector/HttpClientCollector.php +++ b/src/Collector/HttpClientCollector.php @@ -4,6 +4,10 @@ namespace Yiisoft\Yii\Debug\Collector; +use GuzzleHttp\Psr7\Message; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + final class HttpClientCollector implements CollectorInterface, IndexCollectorInterface { use CollectorTrait; @@ -32,34 +36,40 @@ public function getIndexData(): array ]; } - public function collect(\Psr\Http\Message\RequestInterface $request, string $line) + public function collect(RequestInterface $request, float|string $startTime, string $line, ?string $uniqueId) { if (!$this->isActive()) { return; } - $this->requests[spl_object_id($request)][] = [ - 'startTime' => microtime(true), - 'endTime' => microtime(true), + $this->requests[$uniqueId][] = [ + 'startTime' => $startTime, + 'endTime' => $startTime, 'totalTime' => 0, 'method' => $request->getMethod(), 'uri' => $request->getUri()->__toString(), 'headers' => $request->getHeaders(), 'line' => $line, + 'response' => null, ]; } - public function collectTotalTime(\Psr\Http\Message\RequestInterface $request) + public function collectTotalTime(?ResponseInterface $response, float|string $startTime, ?string $uniqueId): void { if (!$this->isActive()) { return; } - if (!isset($this->requests[spl_object_id($request)])) { + if (!isset($this->requests[$uniqueId]) || !is_array($this->requests[$uniqueId])) { return; } - $entry = &$this->requests[spl_object_id($request)][(is_countable($this->requests[spl_object_id($request)]) ? count($this->requests[spl_object_id($request)]) : 0)-1]; - $entry['endTime'] = microtime(true); + $entry = &$this->requests[$uniqueId][count($this->requests[$uniqueId]) - 1]; + if ($response instanceof ResponseInterface) { + $entry['responseRaw'] = Message::toString($response); + $entry['responseStatus'] = $response->getStatusCode(); + Message::rewindBody($response); + } + $entry['endTime'] = $startTime; $entry['totalTime'] = $entry['endTime'] - $entry['startTime']; } } diff --git a/src/Collector/HttpClientInterfaceProxy.php b/src/Collector/HttpClientInterfaceProxy.php index 3d178689..98d899a7 100644 --- a/src/Collector/HttpClientInterfaceProxy.php +++ b/src/Collector/HttpClientInterfaceProxy.php @@ -18,12 +18,17 @@ public function sendRequest(RequestInterface $request): ResponseInterface { [$callStack] = debug_backtrace(); - $this->collector->collect($request, $callStack['file'] . ':' . $callStack['line']); + $uniqueId = random_bytes(36); + $startTime = microtime(true); + $this->collector->collect($request, $startTime, $callStack['file'] . ':' . $callStack['line'], $uniqueId); + $response = null; try { - return $this->decorated->sendRequest($request); + $response = $this->decorated->sendRequest($request); } finally { - $this->collector->collectTotalTime($request); + $endTime = microtime(true); + $this->collector->collectTotalTime($response, $endTime, $uniqueId); + return $response; } } } diff --git a/src/Collector/HttpStreamCollector.php b/src/Collector/HttpStreamCollector.php new file mode 100644 index 00000000..0a9295d5 --- /dev/null +++ b/src/Collector/HttpStreamCollector.php @@ -0,0 +1,77 @@ +requests; + } + + public function startup(): void + { + $this->isActive = true; + HttpStreamProxy::register(); + // TODO: add cURL support, maybe through proxy? + // https://github.com/php/php-src/issues/10509 + //stream_context_set_default([ + // 'http' => [ + // 'proxy' => 'yii-debug-http://127.0.0.1', + // ], + //]); + HttpStreamProxy::$collector = $this; + } + + public function shutdown(): void + { + HttpStreamProxy::unregister(); + HttpStreamProxy::$collector = null; + + $this->reset(); + $this->isActive = false; + } + + public function collect(string $operation, string $path, array $args): void + { + if (!$this->isActive()) { + return; + } + + $this->requests[$operation][] = [ + 'uri' => $path, + 'args' => $args, + ]; + } + + public function getIndexData(): array + { + return [ + 'http_stream' => array_merge( + ...array_map( + fn (string $operation) => [ + $operation => is_countable($this->requests[$operation]) ? count( + $this->requests[$operation] + ) : 0, + ], + array_keys($this->requests) + ) + ), + ]; + } + + private function reset(): void + { + $this->requests = []; + } +} diff --git a/src/Collector/HttpStreamProxy.php b/src/Collector/HttpStreamProxy.php new file mode 100644 index 00000000..54b9da5f --- /dev/null +++ b/src/Collector/HttpStreamProxy.php @@ -0,0 +1,261 @@ +decorated = new StreamWrapper(); + $this->decorated->context = $this->context; + } + + public function __call(string $name, array $arguments) + { + try { + self::unregister(); + return $this->decorated->{$name}(...$arguments); + } finally { + self::register(); + } + } + + public function __destruct() + { + foreach ($this->operations as $name => $operation) { + self::$collector->collect( + operation: $name, + path: $operation['path'], + args: $operation['args'], + ); + } + } + + public function __get(string $name) + { + return $this->decorated->{$name}; + } + + public static function register(): void + { + if (self::$registered) { + return; + } + /** + * It's important to trigger autoloader before unregistering the file stream handler + */ + class_exists(StreamWrapper::class); + stream_wrapper_unregister('http'); + stream_wrapper_register('http', self::class, STREAM_IS_URL); + stream_wrapper_unregister('https'); + stream_wrapper_register('https', self::class, STREAM_IS_URL); + self::$registered = true; + } + + public static function unregister(): void + { + if (!self::$registered) { + return; + } + @stream_wrapper_restore('http'); + @stream_wrapper_restore('https'); + self::$registered = false; + } + + public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_read(int $count): string|false + { + if (!$this->ignored) { + $metadata = stream_get_meta_data($this->decorated->stream); + $context = $this->decorated->context === null + ? null + : stream_context_get_options($this->decorated->context); + /** + * @link https://www.php.net/manual/en/context.http.php + */ + $method = $context['http']['method'] ?? $context['https']['method'] ?? 'GET'; + $headers = (array) ($context['http']['header'] ?? $context['https']['header'] ?? []); + + $this->operations['read'] = [ + 'path' => $this->decorated->filename, + 'args' => [ + 'method' => $method, + 'response_headers' => $metadata['wrapper_data'], + 'request_headers' => $headers, + ], + ]; + } + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_set_option(int $option, int $arg1, ?int $arg2): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_tell(): int + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_eof(): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_seek(int $offset, int $whence = SEEK_SET): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_cast(int $castAs) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_stat(): array|false + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function dir_closedir(): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function dir_opendir(string $path, int $options): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function dir_readdir(): false|string + { + if (!$this->ignored) { + $this->operations[__FUNCTION__] = [ + 'path' => $this->decorated->filename, + 'args' => [], + ]; + } + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function dir_rewinddir(): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function mkdir(string $path, int $mode, int $options): bool + { + if (!$this->ignored) { + $this->operations[__FUNCTION__] = [ + 'path' => $path, + 'args' => [ + 'mode' => $mode, + 'options' => $options, + ], + ]; + } + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function rename(string $path_from, string $path_to): bool + { + if (!$this->ignored) { + $this->operations[__FUNCTION__] = [ + 'path' => $path_from, + 'args' => [ + 'path_to' => $path_to, + ], + ]; + } + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function rmdir(string $path, int $options): bool + { + if (!$this->ignored) { + $this->operations[__FUNCTION__] = [ + 'path' => $path, + 'args' => [ + 'options' => $options, + ], + ]; + } + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_close(): void + { + $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_flush(): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_lock(int $operation): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_metadata(string $path, int $option, mixed $value): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_truncate(int $new_size): bool + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function stream_write(string $data): int + { + if (!$this->ignored) { + $this->operations['write'] = [ + 'path' => $this->decorated->filename, + 'args' => [], + ]; + } + + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function unlink(string $path): bool + { + if (!$this->ignored) { + $this->operations[__FUNCTION__] = [ + 'path' => $path, + 'args' => [], + ]; + } + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function url_stat(string $path, int $flags): array|false + { + return $this->__call(__FUNCTION__, func_get_args()); + } +} diff --git a/src/Collector/RequestCollector.php b/src/Collector/RequestCollector.php index c72a2b96..e08f2b27 100644 --- a/src/Collector/RequestCollector.php +++ b/src/Collector/RequestCollector.php @@ -45,6 +45,18 @@ public function getCollected(): array } } + $requestRaw = null; + if ($this->request instanceof ServerRequestInterface) { + $requestRaw = Message::toString($this->request); + Message::rewindBody($this->request); + } + + $responseRaw = null; + if ($this->response instanceof ResponseInterface) { + $responseRaw = Message::toString($this->response); + Message::rewindBody($this->response); + } + return [ 'requestUrl' => $this->requestUrl, 'requestPath' => $this->requestPath, @@ -54,9 +66,9 @@ public function getCollected(): array 'userIp' => $this->userIp, 'responseStatusCode' => $this->responseStatusCode, 'request' => $this->request, - 'requestRaw' => Message::toString($this->request), + 'requestRaw' => $requestRaw, 'response' => $this->response, - 'responseRaw' => $this->response instanceof ResponseInterface ? Message::toString($this->response) : null, + 'responseRaw' => $responseRaw, 'content' => $content, ]; } diff --git a/src/Helper/StreamWrapper/StreamWrapper.php b/src/Helper/StreamWrapper/StreamWrapper.php new file mode 100644 index 00000000..f463df70 --- /dev/null +++ b/src/Helper/StreamWrapper/StreamWrapper.php @@ -0,0 +1,204 @@ +stream); + return is_resource($this->stream); + } + + public function dir_opendir(string $path, int $options): bool + { + $this->filename = $path; + $this->stream = opendir($path, $this->context); + return is_resource($this->stream); + } + + public function dir_readdir(): false|string + { + return readdir($this->stream); + } + + public function dir_rewinddir(): bool + { + if (!is_resource($this->stream)) { + return false; + } + + return rewinddir($this->stream); + } + + public function mkdir(string $path, int $mode, int $options): bool + { + $this->filename = $path; + return mkdir($path, $mode, ($options & STREAM_MKDIR_RECURSIVE) === STREAM_MKDIR_RECURSIVE, $this->context); + } + + public function rename(string $path_from, string $path_to): bool + { + return rename($path_from, $path_to, $this->context); + } + + public function rmdir(string $path, int $options): bool + { + return rmdir($path, $this->context); + } + + public function stream_cast(int $castAs) + { + //???? + } + + public function stream_eof(): bool + { + return feof($this->stream); + } + + public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool + { + $this->filename = realpath($path) ?: $path; + + if ((self::STREAM_OPEN_FOR_INCLUDE & $options) === self::STREAM_OPEN_FOR_INCLUDE && function_exists('opcache_invalidate')) { + opcache_invalidate($path, false); + } + $this->stream = fopen( + $path, + $mode, + ($options & STREAM_USE_PATH) === STREAM_USE_PATH, + (self::STREAM_OPEN_FOR_INCLUDE & $options) === self::STREAM_OPEN_FOR_INCLUDE ? null : $this->context + ); + + if (!is_resource($this->stream)) { + return false; + } + + if ($opened_path !== null) { + $metaData = stream_get_meta_data($this->stream); + $opened_path = $metaData['uri']; + } + return true; + } + + public function stream_read(int $count): string|false + { + return fread($this->stream, $count); + } + + public function stream_seek(int $offset, int $whence = SEEK_SET): bool + { + return fseek($this->stream, $offset, $whence) !== -1; + } + + public function stream_set_option(int $option, int $arg1, int $arg2): bool + { + 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), + STREAM_OPTION_WRITE_BUFFER => stream_set_write_buffer($this->stream, $arg2) === 0, + default => false, + }; + } + + public function stream_stat(): array|false + { + return fstat($this->stream); + } + + public function stream_tell(): int + { + return ftell($this->stream); + } + + public function stream_write(string $data): int + { + return fwrite($this->stream, $data); + } + + public function url_stat(string $path, int $flags): array|false + { + try { + if (($flags & STREAM_URL_STAT_QUIET) === STREAM_URL_STAT_QUIET) { + return @stat($path); + } + return stat($path); + } catch (Throwable $e) { + if (($flags & STREAM_URL_STAT_QUIET) === STREAM_URL_STAT_QUIET) { + return false; + } + trigger_error($e->getMessage(), E_USER_ERROR); + } + + return false; + } + + public function stream_metadata(string $path, int $option, mixed $value): bool + { + return match ($option) { + STREAM_META_TOUCH => touch($path, ...$value), + STREAM_META_OWNER_NAME, STREAM_META_OWNER => chown($path, $value), + STREAM_META_GROUP_NAME, STREAM_META_GROUP => chgrp($path, $value), + STREAM_META_ACCESS => chmod($path, $value), + default => false + }; + } + + public function stream_flush(): bool + { + return fflush($this->stream); + } + + public function stream_close(): void + { + /** + * @psalm-suppress InvalidPropertyAssignmentValue + */ + fclose($this->stream); + $this->stream = null; + } + + public function stream_lock(int $operation): bool + { + if ($operation === 0) { + $operation = LOCK_EX; + } + return flock($this->stream, $operation); + } + + public function stream_truncate(int $new_size): bool + { + return ftruncate($this->stream, $new_size); + } + + public function unlink(string $path): bool + { + return unlink($path, $this->context); + } +} diff --git a/src/Helper/StreamWrapper/StreamWrapperInterface.php b/src/Helper/StreamWrapper/StreamWrapperInterface.php new file mode 100644 index 00000000..76443a70 --- /dev/null +++ b/src/Helper/StreamWrapper/StreamWrapperInterface.php @@ -0,0 +1,59 @@ +getCollector(); + $collector->startup(); $this->collectTestData($collector); - $this->checkCollectedData($collector); - $this->checkIndexData($collector); + $data = $collector->getCollected(); + if ($collector instanceof IndexCollectorInterface) { + $indexData = $collector->getIndexData(); + } $collector->shutdown(); + $this->assertSame($collector::class, $collector->getName()); + $this->checkCollectedData($data); + if ($collector instanceof IndexCollectorInterface) { + $this->checkIndexData($indexData); + } } abstract protected function getCollector(): CollectorInterface; abstract protected function collectTestData(CollectorInterface $collector): void; - protected function checkCollectedData(CollectorInterface $collector): void + protected function checkCollectedData(array $data): void { - $this->assertNotEmpty($collector->getCollected()); + $this->assertNotEmpty($data); } - protected function checkIndexData(CollectorInterface $collector): void + protected function checkIndexData(array $data): void { - if ($collector instanceof IndexCollectorInterface) { - $this->assertNotEmpty($collector->getIndexData()); - } + $this->assertNotEmpty($data); } } diff --git a/tests/Collector/AssetCollectorTest.php b/tests/Collector/AssetCollectorTest.php index e7409a7c..ae60c120 100644 --- a/tests/Collector/AssetCollectorTest.php +++ b/tests/Collector/AssetCollectorTest.php @@ -8,10 +8,10 @@ use Yiisoft\Yii\Debug\Collector\AssetCollector; use Yiisoft\Yii\Debug\Collector\CollectorInterface; -final class AssetCollectorTest extends CollectorTestCase +final class AssetCollectorTest extends AbstractCollectorTestCase { /** - * @param \Yiisoft\Yii\Debug\Collector\AssetCollector|\Yiisoft\Yii\Debug\Collector\CollectorInterface $collector + * @param AssetCollector|CollectorInterface $collector */ protected function collectTestData(CollectorInterface $collector): void { diff --git a/tests/Collector/CommandCollectorTest.php b/tests/Collector/CommandCollectorTest.php index 7331d49f..08f69fc8 100644 --- a/tests/Collector/CommandCollectorTest.php +++ b/tests/Collector/CommandCollectorTest.php @@ -14,7 +14,7 @@ use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\CommandCollector; -final class CommandCollectorTest extends CollectorTestCase +final class CommandCollectorTest extends AbstractCollectorTestCase { /** * @param CollectorInterface|CommandCollector $collector @@ -59,24 +59,20 @@ protected function getCollector(): CollectorInterface return new CommandCollector(); } - protected function checkCollectedData(CollectorInterface $collector): void + protected function checkCollectedData(array $data): void { - parent::checkCollectedData($collector); - $collected = $collector->getCollected(); - $this->assertCount(3, $collected); - $this->assertEquals('test', $collected[ConsoleCommandEvent::class]['input']); - $this->assertEmpty($collected[ConsoleCommandEvent::class]['output']); + parent::checkCollectedData($data); + $this->assertCount(3, $data); + $this->assertEquals('test', $data[ConsoleCommandEvent::class]['input']); + $this->assertEmpty($data[ConsoleCommandEvent::class]['output']); } - protected function checkIndexData(CollectorInterface $collector): void + protected function checkIndexData(array $data): void { - parent::checkIndexData($collector); - if ($collector instanceof CommandCollector) { - $this->assertArrayHasKey('command', $collector->getIndexData()); - $this->assertArrayHasKey('input', $collector->getIndexData()['command']); - $this->assertArrayHasKey('class', $collector->getIndexData()['command']); - $this->assertEquals('test1', $collector->getIndexData()['command']['input']); - $this->assertEquals(null, $collector->getIndexData()['command']['class']); - } + $this->assertArrayHasKey('command', $data); + $this->assertArrayHasKey('input', $data['command']); + $this->assertArrayHasKey('class', $data['command']); + $this->assertEquals('test1', $data['command']['input']); + $this->assertEquals(null, $data['command']['class']); } } diff --git a/tests/Collector/ConsoleAppInfoCollectorTest.php b/tests/Collector/ConsoleAppInfoCollectorTest.php index 0ac31bfd..22fd5fbf 100644 --- a/tests/Collector/ConsoleAppInfoCollectorTest.php +++ b/tests/Collector/ConsoleAppInfoCollectorTest.php @@ -13,7 +13,7 @@ use function sleep; use function usleep; -final class ConsoleAppInfoCollectorTest extends CollectorTestCase +final class ConsoleAppInfoCollectorTest extends AbstractCollectorTestCase { /** * @param CollectorInterface|WebAppInfoCollector $collector @@ -32,11 +32,9 @@ protected function getCollector(): CollectorInterface return new ConsoleAppInfoCollector(); } - protected function checkCollectedData(CollectorInterface $collector): void + protected function checkCollectedData(array $data): void { - parent::checkCollectedData($collector); - - $data = $collector->getCollected(); + parent::checkCollectedData($data); $this->assertGreaterThan(0.122, $data['applicationProcessingTime']); } diff --git a/tests/Collector/DummyCollectorTest.php b/tests/Collector/DummyCollectorTest.php index f446716b..22595c00 100644 --- a/tests/Collector/DummyCollectorTest.php +++ b/tests/Collector/DummyCollectorTest.php @@ -4,9 +4,10 @@ namespace Yiisoft\Yii\Debug\Tests\Collector; +use stdClass; use Yiisoft\Yii\Debug\Collector\CollectorInterface; -final class DummyCollectorTest extends CollectorTestCase +final class DummyCollectorTest extends AbstractCollectorTestCase { protected function getCollector(): CollectorInterface { @@ -16,7 +17,7 @@ protected function getCollector(): CollectorInterface [ 'int' => 123, 'str' => 'asdas', - 'object' => new \stdClass(), + 'object' => new stdClass(), ] ); $collector->method('getName') diff --git a/tests/Collector/EventCollectorTest.php b/tests/Collector/EventCollectorTest.php index abddab9f..fec26635 100644 --- a/tests/Collector/EventCollectorTest.php +++ b/tests/Collector/EventCollectorTest.php @@ -8,10 +8,10 @@ use Yiisoft\Yii\Debug\Collector\EventCollector; use Yiisoft\Yii\Debug\Tests\Support\DummyEvent; -final class EventCollectorTest extends CollectorTestCase +final class EventCollectorTest extends AbstractCollectorTestCase { /** - * @param \Yiisoft\Yii\Debug\Collector\CollectorInterface|\Yiisoft\Yii\Debug\Collector\EventCollector $collector + * @param CollectorInterface|EventCollector $collector */ protected function collectTestData(CollectorInterface $collector): void { @@ -23,9 +23,8 @@ protected function getCollector(): CollectorInterface return new EventCollector(); } - protected function checkCollectedData(CollectorInterface $collector): void + protected function checkCollectedData(array $data): void { - parent::checkCollectedData($collector); - $this->assertFileExists($collector->getCollected()[0]['file']); + $this->assertFileExists($data[0]['file']); } } diff --git a/tests/Collector/FilesystemStreamCollectorTest.php b/tests/Collector/FilesystemStreamCollectorTest.php new file mode 100644 index 00000000..637af524 --- /dev/null +++ b/tests/Collector/FilesystemStreamCollectorTest.php @@ -0,0 +1,76 @@ +collect( + operation: 'read', + path: __FILE__, + args: ['arg1' => 'v1', 'arg2' => 'v2'], + ); + $collector->collect( + operation: 'read', + path: __FILE__, + args: ['arg3' => 'v3', 'arg4' => 'v4'], + ); + $collector->collect( + operation: 'mkdir', + path: __DIR__, + args: ['recursive'], + ); + } + + public function testCollectWithInactiveCollector(): void + { + $collector = $this->getCollector(); + $this->collectTestData($collector); + + $collected = $collector->getCollected(); + $this->assertEmpty($collected); + } + + protected function getCollector(): CollectorInterface + { + return new FilesystemStreamCollector(); + } + + protected function checkCollectedData(array $data): void + { + parent::checkCollectedData($data); + $collected = $data; + $this->assertCount(2, $collected); + + $this->assertCount(2, $collected['read']); + $this->assertEquals([ + ['path' => __FILE__, 'args' => ['arg1' => 'v1', 'arg2' => 'v2']], + ['path' => __FILE__, 'args' => ['arg3' => 'v3', 'arg4' => 'v4']], + ], $collected['read']); + + $this->assertCount(1, $collected['mkdir']); + $this->assertEquals([ + ['path' => __DIR__, 'args' => ['recursive']], + ], $collected['mkdir']); + } + + protected function checkIndexData(array $data): void + { + parent::checkIndexData($data); + $this->assertArrayHasKey('fs_stream', $data); + $this->assertEquals( + ['read' => 2, 'mkdir' => 1], + $data['fs_stream'], + print_r($data, true), + ); + } +} diff --git a/tests/Collector/HttpStreamCollectorTest.php b/tests/Collector/HttpStreamCollectorTest.php new file mode 100644 index 00000000..9771547b --- /dev/null +++ b/tests/Collector/HttpStreamCollectorTest.php @@ -0,0 +1,53 @@ +collect( + operation: 'read', + path: __FILE__, + args: ['arg1' => 'v1', 'arg2' => 'v2'], + ); + $collector->collect( + operation: 'read', + path: __FILE__, + args: ['arg3' => 'v3', 'arg4' => 'v4'], + ); + } + + protected function getCollector(): CollectorInterface + { + return new HttpStreamCollector(); + } + + protected function checkCollectedData(array $data): void + { + parent::checkCollectedData($data); + $collected = $data; + $this->assertCount(1, $collected); + + $this->assertCount(2, $collected['read']); + $this->assertEquals([ + ['uri' => __FILE__, 'args' => ['arg1' => 'v1', 'arg2' => 'v2']], + ['uri' => __FILE__, 'args' => ['arg3' => 'v3', 'arg4' => 'v4']], + ], $collected['read']); + } + + protected function checkIndexData(array $data): void + { + parent::checkIndexData($data); + $this->assertArrayHasKey('http_stream', $data); + $this->assertEquals(['read' => 2], $data['http_stream']); + } +} diff --git a/tests/Collector/LogCollectorTest.php b/tests/Collector/LogCollectorTest.php index 4ab3b129..e3ece9e5 100644 --- a/tests/Collector/LogCollectorTest.php +++ b/tests/Collector/LogCollectorTest.php @@ -8,10 +8,10 @@ use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\LogCollector; -final class LogCollectorTest extends CollectorTestCase +final class LogCollectorTest extends AbstractCollectorTestCase { /** - * @param \Yiisoft\Yii\Debug\Collector\CollectorInterface|\Yiisoft\Yii\Debug\Collector\LogCollector $collector + * @param CollectorInterface|LogCollector $collector */ protected function collectTestData(CollectorInterface $collector): void { diff --git a/tests/Collector/MiddlewareCollectorTest.php b/tests/Collector/MiddlewareCollectorTest.php index 0c22acc4..75e8be86 100644 --- a/tests/Collector/MiddlewareCollectorTest.php +++ b/tests/Collector/MiddlewareCollectorTest.php @@ -6,6 +6,7 @@ use Nyholm\Psr7\Response; use Nyholm\Psr7\ServerRequest; +use Psr\Http\Server\MiddlewareInterface; use Yiisoft\Di\Container; use Yiisoft\Di\ContainerConfig; use Yiisoft\Middleware\Dispatcher\Event\AfterMiddleware; @@ -15,10 +16,10 @@ use Yiisoft\Yii\Debug\Collector\MiddlewareCollector; use Yiisoft\Yii\Debug\Tests\Support\DummyMiddleware; -final class MiddlewareCollectorTest extends CollectorTestCase +final class MiddlewareCollectorTest extends AbstractCollectorTestCase { /** - * @param \Yiisoft\Yii\Debug\Collector\CollectorInterface|\Yiisoft\Yii\Debug\Collector\MiddlewareCollector $collector + * @param CollectorInterface|MiddlewareCollector $collector */ protected function collectTestData(CollectorInterface $collector): void { @@ -35,11 +36,9 @@ protected function getCollector(): CollectorInterface return new MiddlewareCollector(); } - protected function checkCollectedData(CollectorInterface $collector): void + protected function checkCollectedData(array $data): void { - parent::checkCollectedData($collector); - - $data = $collector->getCollected(); + parent::checkCollectedData($data); $this->assertNotEmpty($data['beforeStack']); $this->assertNotEmpty($data['afterStack']); @@ -48,7 +47,7 @@ protected function checkCollectedData(CollectorInterface $collector): void $this->assertEquals('GET', $data['actionHandler']['request']->getMethod()); } - private function createCallableMiddleware(callable|array $callable): \Psr\Http\Server\MiddlewareInterface + private function createCallableMiddleware(callable|array $callable): MiddlewareInterface { $factory = new MiddlewareFactory(new Container(ContainerConfig::create())); return $factory->create($callable); diff --git a/tests/Collector/QueueCollectorTest.php b/tests/Collector/QueueCollectorTest.php index 57146a83..7d7cdee1 100644 --- a/tests/Collector/QueueCollectorTest.php +++ b/tests/Collector/QueueCollectorTest.php @@ -7,13 +7,12 @@ use Yiisoft\Validator\Result; use Yiisoft\Validator\Rule\Number; use Yiisoft\Yii\Debug\Collector\CollectorInterface; -use Yiisoft\Yii\Debug\Collector\IndexCollectorInterface; use Yiisoft\Yii\Debug\Collector\QueueCollector; use Yiisoft\Yii\Debug\Tests\Support\DummyQueue; use Yiisoft\Yii\Queue\Enum\JobStatus; use Yiisoft\Yii\Queue\Message\Message; -final class QueueCollectorTest extends CollectorTestCase +final class QueueCollectorTest extends AbstractCollectorTestCase { private Message $pushMessage; @@ -54,14 +53,14 @@ protected function getCollector(): CollectorInterface return new QueueCollector(); } - protected function checkCollectedData(CollectorInterface $collector): void + protected function checkCollectedData(array $data): void { - parent::checkCollectedData($collector); + parent::checkCollectedData($data); [ 'pushes' => $pushes, 'statuses' => $statuses, 'processingMessages' => $processingMessages, - ] = $collector->getCollected(); + ] = $data; $this->assertEquals([ 'chan1' => [ @@ -97,14 +96,14 @@ protected function checkCollectedData(CollectorInterface $collector): void ); } - protected function checkIndexData(CollectorInterface|IndexCollectorInterface $collector): void + protected function checkIndexData(array $data): void { - parent::checkIndexData($collector); + parent::checkIndexData($data); [ 'countPushes' => $countPushes, 'countStatuses' => $countStatuses, 'countProcessingMessages' => $countProcessingMessages, - ] = $collector->getIndexData()['queue']; + ] = $data['queue']; $this->assertEquals(2, $countPushes); $this->assertEquals(1, $countStatuses); diff --git a/tests/Collector/RequestCollectorTest.php b/tests/Collector/RequestCollectorTest.php index ea48f0d0..f61cd7b2 100644 --- a/tests/Collector/RequestCollectorTest.php +++ b/tests/Collector/RequestCollectorTest.php @@ -13,7 +13,7 @@ use Yiisoft\Yii\Http\Event\AfterRequest; use Yiisoft\Yii\Http\Event\BeforeRequest; -final class RequestCollectorTest extends CollectorTestCase +final class RequestCollectorTest extends AbstractCollectorTestCase { /** * @param CollectorInterface|RequestCollector $collector @@ -24,6 +24,8 @@ protected function collectTestData(CollectorInterface $collector): void $responseMock = $this->createMock(ResponseInterface::class); $uriMock = $this->createMock(UriInterface::class); $bodyMock = $this->createMock(StreamInterface::class); + $bodyMock->method('tell') + ->willReturn(1); $uriMock->method('getPath') ->willReturn('url'); @@ -40,6 +42,8 @@ protected function collectTestData(CollectorInterface $collector): void ->willReturn(''); $requestMock->method('getUri') ->willReturn($uriMock); + $requestMock->method('getBody') + ->willReturn($bodyMock); $responseMock->method('getStatusCode') ->willReturn(200); @@ -57,22 +61,18 @@ protected function getCollector(): CollectorInterface return new RequestCollector(); } - protected function checkCollectedData(CollectorInterface $collector): void + protected function checkCollectedData(array $data): void { - parent::checkCollectedData($collector); - $this->assertInstanceOf(ServerRequestInterface::class, $collector->getCollected()['request']); - $this->assertInstanceOf(ResponseInterface::class, $collector->getCollected()['response']); + parent::checkCollectedData($data); + $this->assertInstanceOf(ServerRequestInterface::class, $data['request']); + $this->assertInstanceOf(ResponseInterface::class, $data['response']); } - protected function checkIndexData(CollectorInterface $collector): void + protected function checkIndexData(array $data): void { - parent::checkIndexData($collector); - if ($collector instanceof RequestCollector) { - $data = $collector->getIndexData(); - - $this->assertEquals('http://test.site/url', $data['request']['url']); - $this->assertEquals('GET', $data['request']['method']); - $this->assertEquals(200, $data['response']['statusCode']); - } + parent::checkIndexData($data); + $this->assertEquals('http://test.site/url', $data['request']['url']); + $this->assertEquals('GET', $data['request']['method']); + $this->assertEquals(200, $data['response']['statusCode']); } } diff --git a/tests/Collector/RouterCollectorTest.php b/tests/Collector/RouterCollectorTest.php index 106be546..2f029354 100644 --- a/tests/Collector/RouterCollectorTest.php +++ b/tests/Collector/RouterCollectorTest.php @@ -4,6 +4,7 @@ namespace Yiisoft\Yii\Debug\Tests\Collector; +use PHPUnit\Framework\MockObject\MockObject; use Yiisoft\Di\Container; use Yiisoft\Di\ContainerConfig; use Yiisoft\Router\Group; @@ -16,14 +17,14 @@ use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\RouterCollector; -final class RouterCollectorTest extends CollectorTestCase +final class RouterCollectorTest extends AbstractCollectorTestCase { - private \PHPUnit\Framework\MockObject\MockObject|\Yiisoft\Router\RouteCollectorInterface|null $routeCollector = null; + private MockObject|RouteCollectorInterface|null $routeCollector = null; private ?Container $container = null; /** - * @param \Yiisoft\Yii\Debug\Collector\CollectorInterface|\Yiisoft\Yii\Debug\Collector\RouterCollector $collector + * @param CollectorInterface|RouterCollector $collector */ protected function collectTestData(CollectorInterface $collector): void { @@ -51,23 +52,23 @@ protected function getCollector(): CollectorInterface return new RouterCollector($this->container); } - protected function checkCollectedData(CollectorInterface $collector): void + protected function checkCollectedData(array $data): void { - parent::checkCollectedData($collector); - $this->assertArrayHasKey('routes', $collector->getCollected()); - $this->assertArrayHasKey('routesTree', $collector->getCollected()); - $this->assertArrayHasKey('routeTime', $collector->getCollected()); + parent::checkCollectedData($data); + $this->assertArrayHasKey('routes', $data); + $this->assertArrayHasKey('routesTree', $data); + $this->assertArrayHasKey('routeTime', $data); $this->assertEquals( $this->container->get(RouteCollectionInterface::class)->getRoutes(), - $collector->getCollected()['routes'] + $data['routes'] ); $this->assertEquals( $this->container->get(RouteCollectionInterface::class)->getRouteTree(), - $collector->getCollected()['routesTree'] + $data['routesTree'] ); $this->assertEquals( 0.001, - $collector->getCollected()['routeTime'] + $data['routeTime'] ); } diff --git a/tests/Collector/ServiceCollectorTest.php b/tests/Collector/ServiceCollectorTest.php index cf457a0e..feb5cd58 100644 --- a/tests/Collector/ServiceCollectorTest.php +++ b/tests/Collector/ServiceCollectorTest.php @@ -8,7 +8,7 @@ use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\ServiceCollector; -final class ServiceCollectorTest extends CollectorTestCase +final class ServiceCollectorTest extends AbstractCollectorTestCase { /** * @param CollectorInterface|ServiceCollector $collector diff --git a/tests/Collector/ValidatorCollectorTest.php b/tests/Collector/ValidatorCollectorTest.php index 9e7c7d5c..e5997aa7 100644 --- a/tests/Collector/ValidatorCollectorTest.php +++ b/tests/Collector/ValidatorCollectorTest.php @@ -9,7 +9,7 @@ use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\ValidatorCollector; -final class ValidatorCollectorTest extends CollectorTestCase +final class ValidatorCollectorTest extends AbstractCollectorTestCase { /** * @param CollectorInterface|ValidatorCollector $collector diff --git a/tests/Collector/ValidatorInterfaceProxyTest.php b/tests/Collector/ValidatorInterfaceProxyTest.php index 589c838f..c1dc5fc0 100644 --- a/tests/Collector/ValidatorInterfaceProxyTest.php +++ b/tests/Collector/ValidatorInterfaceProxyTest.php @@ -4,27 +4,64 @@ namespace Yiisoft\Yii\Debug\Tests\Collector; -use PHPUnit\Framework\TestCase; +use Yiisoft\Validator\Error; +use Yiisoft\Validator\Result; use Yiisoft\Validator\Rule\Number; -use Yiisoft\Validator\Validator; +use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\ValidatorCollector; -use Yiisoft\Yii\Debug\Collector\ValidatorInterfaceProxy; -final class ValidatorInterfaceProxyTest extends TestCase +final class ValidatorInterfaceProxyTest extends AbstractCollectorTestCase { - public function testBase(): void + /** + * @param CollectorInterface|ValidatorCollector $collector + */ + protected function collectTestData(CollectorInterface $collector): void { - $validator = new Validator(); - $collector = new ValidatorCollector(); + $collector->collect(1, (new Result())->addError('Too low', ['arg1' => 'v1']), [new Number(min: 7)]); + $collector->collect(10, new Result(), [new Number(min: 7)]); + } - $proxy = new ValidatorInterfaceProxy($validator, $collector); + protected function getCollector(): CollectorInterface + { + return new ValidatorCollector(); + } + + protected function checkCollectedData(array $data): void + { + parent::checkCollectedData($data); - $collector->startup(); - $proxy->validate(1, [new Number(min: 7)]); + $this->assertEquals( + [ + [ + 'value' => 1, + 'rules' => [ + new Number(min: 7), + ], + 'result' => false, + 'errors' => [ + new Error('Too low', ['arg1' => 'v1']), + ], + ], + [ + 'value' => 10, + 'rules' => [ + new Number(min: 7), + ], + 'result' => true, + 'errors' => [], + ], + ], + $data + ); + } + + protected function checkIndexData(array $data): void + { + parent::checkIndexData($data); - $this->assertSame( - ['validator' => ['total' => 1, 'valid' => 0, 'invalid' => 1]], - $collector->getIndexData() + $this->assertEquals( + ['total' => 2, 'valid' => 1, 'invalid' => 1], + $data['validator'] ); } } diff --git a/tests/Collector/WebAppInfoCollectorTest.php b/tests/Collector/WebAppInfoCollectorTest.php index f8783bb3..c489742a 100644 --- a/tests/Collector/WebAppInfoCollectorTest.php +++ b/tests/Collector/WebAppInfoCollectorTest.php @@ -14,10 +14,10 @@ use function sleep; use function usleep; -final class WebAppInfoCollectorTest extends CollectorTestCase +final class WebAppInfoCollectorTest extends AbstractCollectorTestCase { /** - * @param WebAppInfoCollector|\Yiisoft\Yii\Debug\Collector\CollectorInterface $collector + * @param CollectorInterface|WebAppInfoCollector $collector */ protected function collectTestData(CollectorInterface $collector): void { @@ -35,11 +35,9 @@ protected function getCollector(): CollectorInterface return new WebAppInfoCollector(); } - protected function checkCollectedData(CollectorInterface $collector): void + protected function checkCollectedData(array $data): void { - parent::checkCollectedData($collector); - - $data = $collector->getCollected(); + parent::checkCollectedData($data); $this->assertGreaterThan(0.122, $data['requestProcessingTime']); } diff --git a/tests/Collector/WebViewCollectorTest.php b/tests/Collector/WebViewCollectorTest.php index 884e15e0..c63394b0 100644 --- a/tests/Collector/WebViewCollectorTest.php +++ b/tests/Collector/WebViewCollectorTest.php @@ -10,10 +10,10 @@ use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\WebViewCollector; -final class WebViewCollectorTest extends CollectorTestCase +final class WebViewCollectorTest extends AbstractCollectorTestCase { /** - * @param \Yiisoft\Yii\Debug\Collector\CollectorInterface|\Yiisoft\Yii\Debug\Collector\WebViewCollector $collector + * @param CollectorInterface|WebViewCollector $collector */ protected function collectTestData(CollectorInterface $collector): void {