From dbcbeda8285924a3da3d7fb94989405ad931207c Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 27 Mar 2023 00:27:49 +0300 Subject: [PATCH 01/11] Split yii-debug package codebase --- composer.json | 6 + config/params.php | 18 + src/Debug/CommandInterfaceProxy.php | 396 ++++++++++++++++++++ src/Debug/ConnectionInterfaceProxy.php | 163 ++++++++ src/Debug/DatabaseCollector.php | 163 ++++++++ src/Debug/TransactionInterfaceDecorator.php | 79 ++++ 6 files changed, 825 insertions(+) create mode 100644 config/params.php create mode 100644 src/Debug/CommandInterfaceProxy.php create mode 100644 src/Debug/ConnectionInterfaceProxy.php create mode 100644 src/Debug/DatabaseCollector.php create mode 100644 src/Debug/TransactionInterfaceDecorator.php diff --git a/composer.json b/composer.json index 6112aa1fd..ed27968f5 100644 --- a/composer.json +++ b/composer.json @@ -61,6 +61,12 @@ "extra": { "branch-alias": { "dev-master": "3.0.x-dev" + }, + "config-plugin-options": { + "source-directory": "config" + }, + "config-plugin": { + "params": "params.php" } }, "config": { diff --git a/config/params.php b/config/params.php new file mode 100644 index 000000000..a06a4c1e4 --- /dev/null +++ b/config/params.php @@ -0,0 +1,18 @@ + [ + 'collectors' => [ + DatabaseCollector::class, + ], + 'trackedServices' => [ + ConnectionInterface::class => [ConnectionInterfaceProxy::class, DatabaseCollector::class], + ], + ], +]; diff --git a/src/Debug/CommandInterfaceProxy.php b/src/Debug/CommandInterfaceProxy.php new file mode 100644 index 000000000..066821afa --- /dev/null +++ b/src/Debug/CommandInterfaceProxy.php @@ -0,0 +1,396 @@ +decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function addColumn(string $table, string $column, string $type): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function addCommentOnColumn(string $table, string $column, string $comment): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function addCommentOnTable(string $table, string $comment): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function addDefaultValue(string $name, string $table, string $column, mixed $value): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function addForeignKey( + string $name, + string $table, + array|string $columns, + string $refTable, + array|string $refColumns, + string $delete = null, + string $update = null + ): static { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function addPrimaryKey(string $name, string $table, array|string $columns): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function addUnique(string $name, string $table, array|string $columns): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function alterColumn(string $table, string $column, string $type): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function batchInsert(string $table, array $columns, iterable $rows): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function bindParam( + int|string $name, + mixed &$value, + int $dataType = null, + int $length = null, + mixed $driverOptions = null + ): static { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function bindValue(int|string $name, mixed $value, int $dataType = null): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function bindValues(array $values): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function cancel(): void + { + $this->decorated->{__FUNCTION__}(...func_get_args()); + } + + public function checkIntegrity(string $schema, string $table, bool $check = true): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function createIndex( + string $name, + string $table, + array|string $columns, + string $indexType = null, + string $indexMethod = null + ): static { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function createTable(string $table, array $columns, string $options = null): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function createView(string $viewName, QueryInterface|string $subQuery): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function delete(string $table, array|string $condition = '', array $params = []): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function dropCheck(string $name, string $table): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function dropColumn(string $table, string $column): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function dropCommentFromColumn(string $table, string $column): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function dropCommentFromTable(string $table): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function dropDefaultValue(string $name, string $table): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function dropForeignKey(string $name, string $table): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function dropIndex(string $name, string $table): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function dropPrimaryKey(string $name, string $table): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function dropTable(string $table): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function dropUnique(string $name, string $table): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function dropView(string $viewName): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function execute(): int + { + [$callStack] = debug_backtrace(); + + $id = random_bytes(36); + $this->collectQueryStart($id, $callStack['file'] . ':' . $callStack['line']); + try { + $result = $this->decorated->execute(); + } catch (Throwable $e) { + $this->collectQueryError($id, $e); + throw $e; + } + $this->collectQueryEnd($id, $result); + return $result; + } + + public function getParams(bool $asValues = true): array + { + return $this->decorated->getParams($asValues); + } + + public function getRawSql(): string + { + return $this->decorated->getRawSql(); + } + + public function getSql(): string + { + return $this->decorated->getSql(); + } + + public function insert(string $table, QueryInterface|array $columns): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function insertWithReturningPks(string $table, array $columns): bool|array + { + return $this->decorated->{__FUNCTION__}(...func_get_args()); + } + + public function prepare(bool $forRead = null): void + { + $this->decorated->{__FUNCTION__}(...func_get_args()); + } + + public function query(): DataReaderInterface + { + [$callStack] = debug_backtrace(); + + $id = random_bytes(36); + $this->collectQueryStart($id, $callStack['file'] . ':' . $callStack['line']); + try { + $result = $this->decorated->query(); + } catch (Throwable $e) { + $this->collectQueryError($id, $e); + throw $e; + } + $rowsNumber = $result->count(); + $this->collectQueryEnd($id, $rowsNumber); + return $result; + } + + public function queryAll(): array + { + [$callStack] = debug_backtrace(); + + $id = random_bytes(36); + $this->collectQueryStart($id, $callStack['file'] . ':' . $callStack['line']); + try { + $result = $this->decorated->queryAll(); + } catch (Throwable $e) { + $this->collectQueryError($id, $e); + throw $e; + } + $this->collectQueryEnd($id, count($result)); + return $result; + } + + public function queryBuilder(): QueryBuilderInterface + { + return $this->decorated->{__FUNCTION__}(...func_get_args()); + } + + public function queryColumn(): array + { + [$callStack] = debug_backtrace(); + + $id = random_bytes(36); + $this->collectQueryStart($id, $callStack['file'] . ':' . $callStack['line']); + try { + $result = $this->decorated->queryColumn(); + } catch (Throwable $e) { + $this->collectQueryError($id, $e); + throw $e; + } + $this->collectQueryEnd($id, count($result)); + return $result; + } + + public function queryOne(): array|null + { + [$callStack] = debug_backtrace(); + + $id = random_bytes(36); + $this->collectQueryStart($id, $callStack['file'] . ':' . $callStack['line']); + try { + $result = $this->decorated->queryOne(); + } catch (Throwable $e) { + $this->collectQueryError($id, $e); + throw $e; + } + $this->collectQueryEnd($id, $result === null ? 0 : 1); + return $result; + } + + public function queryScalar(): bool|string|null|int|float + { + [$callStack] = debug_backtrace(); + + $id = random_bytes(36); + $this->collectQueryStart($id, $callStack['file'] . ':' . $callStack['line']); + try { + $result = $this->decorated->queryScalar(); + } catch (Throwable $e) { + $this->collectQueryError($id, $e); + throw $e; + } + $this->collectQueryEnd($id, $result === null ? 0 : 1); + return $result; + } + + public function renameColumn(string $table, string $oldName, string $newName): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function renameTable(string $table, string $newName): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function resetSequence(string $table, int|string $value = null): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function setProfiler(?ProfilerInterface $profiler): void + { + $this->decorated->{__FUNCTION__}(...func_get_args()); + } + + public function setRawSql(string $sql): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function setRetryHandler(?Closure $handler): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function setSql(string $sql): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function truncateTable(string $table): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function update(string $table, array $columns, array|string $condition = '', array $params = []): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + public function upsert( + string $table, + QueryInterface|array $insertColumns, + bool|array $updateColumns = true, + array $params = [] + ): static { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + private function collectQueryStart(string $id, string $line): void + { + $this->collector->collectQueryStart( + id: $id, + sql: $this->decorated->getSql(), + rawSql: $this->decorated->getRawSql(), + params: $this->decorated->getParams(), + line: $line, + ); + } + + private function collectQueryError(string $id, Throwable $exception): void + { + $this->collector->collectQueryError($id, $exception); + } + + private function collectQueryEnd(string $id, int $rowsNumber): void + { + $this->collector->collectQueryEnd($id, $rowsNumber); + } +} diff --git a/src/Debug/ConnectionInterfaceProxy.php b/src/Debug/ConnectionInterfaceProxy.php new file mode 100644 index 000000000..e334a14f5 --- /dev/null +++ b/src/Debug/ConnectionInterfaceProxy.php @@ -0,0 +1,163 @@ +connection->beginTransaction($isolationLevel); + + $this->collector->collectTransactionStart($isolationLevel, $callStack['file'] . ':' . $callStack['line']); + return new TransactionInterfaceDecorator($result, $this->collector); + } + + public function createBatchQueryResult(QueryInterface $query, bool $each = false): BatchQueryResultInterface + { + return $this->connection->createBatchQueryResult($query, $each); + } + + public function createCommand(string $sql = null, array $params = []): CommandInterface + { + return new CommandInterfaceProxy( + $this->connection->createCommand($sql, $params), + $this->collector, + ); + } + + public function createTransaction(): TransactionInterface + { + return new TransactionInterfaceDecorator( + $this->connection->createTransaction(), + $this->collector, + ); + } + + public function close(): void + { + $this->connection->close(); + } + + public function getCacheKey(): array + { + return $this->connection->getCacheKey(); + } + + public function getName(): string + { + return $this->connection->getName(); + } + + public function getLastInsertID(string $sequenceName = null): string + { + return $this->connection->getLastInsertID($sequenceName); + } + + public function getQueryBuilder(): QueryBuilderInterface + { + return $this->connection->getQueryBuilder(); + } + + public function getQuoter(): QuoterInterface + { + return $this->connection->getQuoter(); + } + + public function getSchema(): SchemaInterface + { + return $this->connection->getSchema(); + } + + public function getServerVersion(): string + { + return $this->connection->getServerVersion(); + } + + public function getTablePrefix(): string + { + return $this->connection->getTablePrefix(); + } + + public function getTableSchema(string $name, bool $refresh = false): TableSchemaInterface|null + { + return $this->connection->getTableSchema($name, $refresh); + } + + public function getTransaction(): TransactionInterface|null + { + $result = $this->connection->getTransaction(); + + return $result === null + ? null + : new TransactionInterfaceDecorator( + $result, + $this->collector, + ); + } + + public function isActive(): bool + { + return $this->connection->isActive(); + } + + public function isSavepointEnabled(): bool + { + return $this->connection->isSavepointEnabled(); + } + + public function open(): void + { + $this->connection->open(); + } + + public function quoteValue(mixed $value): mixed + { + return $this->connection->quoteValue($value); + } + + public function setEnableSavepoint(bool $value): void + { + $this->connection->setEnableSavepoint($value); + } + + public function setTablePrefix(string $value): void + { + $this->connection->setTablePrefix($value); + } + + public function transaction(Closure $closure, string $isolationLevel = null): mixed + { + [$callStack] = debug_backtrace(); + + $this->collector->collectTransactionStart($isolationLevel, $callStack['file'] . ':' . $callStack['line']); + + return $this->connection->transaction(fn () => $closure($this), $isolationLevel); + } + + public function setProfiler(?ProfilerInterface $profiler): void + { + $this->connection->setProfiler($profiler); + } +} diff --git a/src/Debug/DatabaseCollector.php b/src/Debug/DatabaseCollector.php new file mode 100644 index 000000000..3e5738f89 --- /dev/null +++ b/src/Debug/DatabaseCollector.php @@ -0,0 +1,163 @@ +queries[$id] = [ + 'position' => $this->position++, + 'transactionId' => $this->currentTransactionId, + 'sql' => $sql, + 'rawSql' => $rawSql, + 'params' => $params, + 'line' => $line, + 'status' => self::QUERY_STATUS_INITIALIZED, + 'actions' => [ + 'action' => self::ACTION_QUERY_START, + 'time' => microtime(true), + ], + ]; + } + + public function collectQueryEnd( + string $id, + int $rowsNumber, + ): void { + $this->queries[$id]['rowsNumber'] = $rowsNumber; + $this->queries[$id]['status'] = self::QUERY_STATUS_SUCCESS; + $this->queries[$id]['actions'][] = [ + 'action' => self::ACTION_QUERY_END, + 'time' => microtime(true), + ]; + } + + public function collectQueryError( + string $id, + Throwable $exception, + ): void { + $this->queries[$id]['exception'] = $exception; + $this->queries[$id]['status'] = self::QUERY_STATUS_ERROR; + $this->queries[$id]['actions'][] = [ + 'action' => self::ACTION_QUERY_ERROR, + 'time' => microtime(true), + ]; + } + + public function collectTransactionStart( + ?string $isolationLevel, + string $line, + ): void { + $id = ++$this->currentTransactionId; + $this->transactions[$id] = [ + 'id' => $id, + 'position' => $this->position++, + 'status' => self::TRANSACTION_STATUS_START, + 'line' => $line, + 'level' => $isolationLevel, + 'actions' => [ + 'action' => self::ACTION_TRANSACTION_START, + 'time' => microtime(true), + ], + ]; + } + + public function collectTransactionRollback( + string $line, + ) { + $this->transactions[$this->currentTransactionId]['status'] = self::TRANSACTION_STATUS_ROLLBACK; + $this->transactions[$this->currentTransactionId]['actions'] = [ + 'action' => self::ACTION_TRANSACTION_ROLLBACK, + 'line' => $line, + 'time' => microtime(true), + ]; + ++$this->currentTransactionId; + } + + public function collectTransactionCommit( + string $line, + ) { + $this->transactions[$this->currentTransactionId]['status'] = self::TRANSACTION_STATUS_COMMIT; + $this->transactions[$this->currentTransactionId]['actions'] = [ + 'action' => self::ACTION_TRANSACTION_COMMIT, + 'line' => $line, + 'time' => microtime(true), + ]; + ++$this->currentTransactionId; + } + + public function getCollected(): array + { + usort($this->queries, fn (array $a, array $b) => $a['position'] <=> $b['position']); + + return [ + 'queries' => array_values($this->queries), + 'transactions' => array_values($this->transactions), + ]; + } + + public function getIndexData(): array + { + return [ + 'db' => [ + 'queries' => [ + 'error' => count( + array_filter($this->queries, fn (array $query) => $query['status'] === self::QUERY_STATUS_ERROR) + ), + 'total' => count($this->queries), + ], + 'transactions' => [ + 'error' => count( + array_filter( + $this->transactions, + fn (array $query) => $query['status'] === self::TRANSACTION_STATUS_ROLLBACK + ) + ), + 'total' => count($this->transactions), + ], + ], + ]; + } + + private function reset(): void + { + $this->queries = []; + } +} diff --git a/src/Debug/TransactionInterfaceDecorator.php b/src/Debug/TransactionInterfaceDecorator.php new file mode 100644 index 000000000..42707499f --- /dev/null +++ b/src/Debug/TransactionInterfaceDecorator.php @@ -0,0 +1,79 @@ +decorated->{__FUNCTION__}(...func_get_args()); + } + + public function begin(string $isolationLevel = null): void + { + [$callStack] = debug_backtrace(); + + $this->collector->collectTransactionStart($isolationLevel, $callStack['file'] . ':' . $callStack['line']); + + $this->decorated->begin($isolationLevel); + } + + public function commit(): void + { + [$callStack] = debug_backtrace(); + + $this->decorated->commit(); + + $this->collector->collectTransactionCommit($callStack['file'] . ':' . $callStack['line']); + } + + public function getLevel(): int + { + return $this->decorated->getLevel(); + } + + public function isActive(): bool + { + return $this->decorated->isActive(); + } + + public function rollBack(): void + { + [$callStack] = debug_backtrace(); + + $this->decorated->rollBack(); + + $this->collector->collectTransactionRollback($callStack['file'] . ':' . $callStack['line']); + } + + public function setIsolationLevel(string $level): void + { + $this->decorated->{__FUNCTION__}(...func_get_args()); + } + + public function createSavepoint(string $name): void + { + $this->decorated->{__FUNCTION__}(...func_get_args()); + } + + public function rollBackSavepoint(string $name): void + { + $this->decorated->{__FUNCTION__}(...func_get_args()); + } + + public function releaseSavepoint(string $name): void + { + $this->decorated->{__FUNCTION__}(...func_get_args()); + } +} From d7a10ffceafcef6c60ad00b06dfe559d3efae697 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sun, 26 Mar 2023 21:28:42 +0000 Subject: [PATCH 02/11] Apply fixes from StyleCI --- src/Schema/Quoter.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Schema/Quoter.php b/src/Schema/Quoter.php index 93300cc67..4461638bd 100644 --- a/src/Schema/Quoter.php +++ b/src/Schema/Quoter.php @@ -6,6 +6,7 @@ use Yiisoft\Db\Exception\InvalidArgumentException; use Yiisoft\Db\Expression\ExpressionInterface; + use function addcslashes; use function explode; use function implode; @@ -221,6 +222,7 @@ public function unquoteSimpleTableName(string $name): string /** * @psalm-param string[] $parts Parts of table name + * * @psalm-return string[] */ protected function unquoteParts(array $parts, bool $withColumn): array From ead64bf2d22c2067d1b4dc03e9747412531c1394 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 27 Mar 2023 11:07:17 +0300 Subject: [PATCH 03/11] Update aliases version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ed27968f5..8e842e1a4 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "roave/infection-static-analysis-plugin": "^1.16", "spatie/phpunit-watcher": "^1.23", "vimeo/psalm": "^4.30|^5.6", - "yiisoft/aliases": "^1.1|^2.0", + "yiisoft/aliases": "^3.0", "yiisoft/cache-file": "^2.0", "yiisoft/di": "^1.0", "yiisoft/event-dispatcher": "^1.0", From cdad6c47b71b3778935aaa918e7e0e05f40d621b Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 27 Mar 2023 11:07:37 +0300 Subject: [PATCH 04/11] Add yii-debug to dev-dependencies --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8e842e1a4..f02fbc37d 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,8 @@ "yiisoft/event-dispatcher": "^1.0", "yiisoft/json": "^1.0", "yiisoft/log": "^2.0", - "yiisoft/var-dumper": "^1.5" + "yiisoft/var-dumper": "^1.5", + "yiisoft/yii-debug": "dev-split-debug" }, "autoload": { "psr-4": { From 077731754ab8a0a3ab3a451b0ef341c2985659ef Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Wed, 29 Mar 2023 09:27:02 +0300 Subject: [PATCH 05/11] Rename IndexCollectorInterface -> SummaryCollectorInterface, collapse with CollectorInterface --- src/Debug/DatabaseCollector.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Debug/DatabaseCollector.php b/src/Debug/DatabaseCollector.php index 3e5738f89..5b618b271 100644 --- a/src/Debug/DatabaseCollector.php +++ b/src/Debug/DatabaseCollector.php @@ -5,11 +5,10 @@ namespace Yiisoft\Db\Debug; use Throwable; -use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\CollectorTrait; -use Yiisoft\Yii\Debug\Collector\IndexCollectorInterface; +use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; -final class DatabaseCollector implements CollectorInterface, IndexCollectorInterface +final class DatabaseCollector implements SummaryCollectorInterface { use CollectorTrait; @@ -133,7 +132,7 @@ public function getCollected(): array ]; } - public function getIndexData(): array + public function getSummary(): array { return [ 'db' => [ From 065c4ec3dcfcbc2308834d482e96eada02549ab9 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 29 Apr 2023 13:57:02 +0300 Subject: [PATCH 06/11] Fix package version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 93c21b4f0..aa7bc525e 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,7 @@ "yiisoft/json": "^1.0", "yiisoft/log": "^2.0", "yiisoft/var-dumper": "^1.5", - "yiisoft/yii-debug": "dev-split-debug" + "yiisoft/yii-debug": "dev-master" }, "autoload": { "psr-4": { From 2da53c66561b7d43900ba6d0d52409754440f62e Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 29 Apr 2023 13:57:06 +0300 Subject: [PATCH 07/11] Add changelog --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0912aba1b..e1ad2ea57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,9 @@ # Yii Database Change Log -## 1.0.1 under development +## 1.1.0 under development -- no changes in this release. +- Enh #617: Add debug collector for yiisoft/yii-debug (@xepozz) ## 1.0.0 April 12, 2023 -- Initial release. \ No newline at end of file +- Initial release. From 7c2a7d210c85271b4ca1926ad3b1b6c4177e27f1 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 29 Apr 2023 14:06:50 +0300 Subject: [PATCH 08/11] Add compposer-require-checker config --- composer-require-checker.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 composer-require-checker.json diff --git a/composer-require-checker.json b/composer-require-checker.json new file mode 100644 index 000000000..2067464bb --- /dev/null +++ b/composer-require-checker.json @@ -0,0 +1,6 @@ +{ + "symbol-whitelist" : [ + "Yiisoft\\Yii\\Debug\\Collector\\CollectorTrait", + "Yiisoft\\Yii\\Debug\\Collector\\SummaryCollectorInterface" + ] +} From 54e130ff0148a014784f17dadf324bbf743db011 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 29 Apr 2023 18:23:13 +0300 Subject: [PATCH 09/11] Fix psalm errors --- src/Connection/ConnectionInterface.php | 2 +- src/Debug/CommandInterfaceProxy.php | 183 +++++++++++++++++--- src/Debug/ConnectionInterfaceProxy.php | 24 ++- src/Debug/DatabaseCollector.php | 55 +++++- src/Debug/TransactionInterfaceDecorator.php | 15 +- 5 files changed, 227 insertions(+), 52 deletions(-) diff --git a/src/Connection/ConnectionInterface.php b/src/Connection/ConnectionInterface.php index 08dfe2abc..c4f2e24d6 100644 --- a/src/Connection/ConnectionInterface.php +++ b/src/Connection/ConnectionInterface.php @@ -212,7 +212,7 @@ public function setTablePrefix(string $value): void; /** * Executes callback provided in a transaction. * - * @param Closure $closure A valid PHP callback that performs the job. Accepts connection instance as parameter. + * @psalm-param Closure(ConnectionInterface): mixed $closure A valid PHP callback that performs the job. Accepts connection instance as parameter. * @param string|null $isolationLevel The isolation level to use for this transaction. * {@see TransactionInterface::begin()} for details. * diff --git a/src/Debug/CommandInterfaceProxy.php b/src/Debug/CommandInterfaceProxy.php index 066821afa..ebbccdd00 100644 --- a/src/Debug/CommandInterfaceProxy.php +++ b/src/Debug/CommandInterfaceProxy.php @@ -7,10 +7,8 @@ use Closure; use Throwable; use Yiisoft\Db\Command\CommandInterface; -use Yiisoft\Db\Profiler\ProfilerInterface; use Yiisoft\Db\Query\Data\DataReaderInterface; use Yiisoft\Db\Query\QueryInterface; -use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; final class CommandInterfaceProxy implements CommandInterface { @@ -20,63 +18,96 @@ public function __construct( ) { } - public function addCheck(string $name, string $table, string $expression): static + /** + * @psalm-suppress MixedArgument + */ + public function addCheck(string $table, string $name, string $expression): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function addColumn(string $table, string $column, string $type): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function addCommentOnColumn(string $table, string $column, string $comment): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function addCommentOnTable(string $table, string $comment): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } - public function addDefaultValue(string $name, string $table, string $column, mixed $value): static + /** + * @psalm-suppress MixedArgument + */ + public function addDefaultValue(string $table, string $name, string $column, mixed $value): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function addForeignKey( - string $name, string $table, + string $name, array|string $columns, - string $refTable, - array|string $refColumns, + string $referenceTable, + array|string $referenceColumns, string $delete = null, string $update = null ): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } - public function addPrimaryKey(string $name, string $table, array|string $columns): static + /** + * @psalm-suppress MixedArgument + */ + public function addPrimaryKey(string $table, string $name, array|string $columns): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } - public function addUnique(string $name, string $table, array|string $columns): static + /** + * @psalm-suppress MixedArgument + */ + public function addUnique(string $table, string $name, array|string $columns): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function alterColumn(string $table, string $column, string $type): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function batchInsert(string $table, array $columns, iterable $rows): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function bindParam( int|string $name, mixed &$value, @@ -87,11 +118,17 @@ public function bindParam( return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function bindValue(int|string $name, mixed $value, int $dataType = null): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function bindValues(array $values): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); @@ -102,14 +139,20 @@ public function cancel(): void $this->decorated->{__FUNCTION__}(...func_get_args()); } + /** + * @psalm-suppress MixedArgument + */ public function checkIntegrity(string $schema, string $table, bool $check = true): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function createIndex( - string $name, string $table, + string $name, array|string $columns, string $indexType = null, string $indexMethod = null @@ -117,76 +160,121 @@ public function createIndex( return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function createTable(string $table, array $columns, string $options = null): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function createView(string $viewName, QueryInterface|string $subQuery): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function delete(string $table, array|string $condition = '', array $params = []): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } - public function dropCheck(string $name, string $table): static + /** + * @psalm-suppress MixedArgument + */ + public function dropCheck(string $table, string $name): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function dropColumn(string $table, string $column): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function dropCommentFromColumn(string $table, string $column): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function dropCommentFromTable(string $table): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } - public function dropDefaultValue(string $name, string $table): static + /** + * @psalm-suppress MixedArgument + */ + public function dropDefaultValue(string $table, string $name): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } - public function dropForeignKey(string $name, string $table): static + /** + * @psalm-suppress MixedArgument + */ + public function dropForeignKey(string $table, string $name): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } - public function dropIndex(string $name, string $table): static + /** + * @psalm-suppress MixedArgument + */ + public function dropIndex(string $table, string $name): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } - public function dropPrimaryKey(string $name, string $table): static + /** + * @psalm-suppress MixedArgument + */ + public function dropPrimaryKey(string $table, string $name): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function dropTable(string $table): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } - public function dropUnique(string $name, string $table): static + /** + * @psalm-suppress MixedArgument + */ + public function dropUnique(string $table, string $name): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function dropView(string $viewName): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress PossiblyUndefinedArrayOffset + */ public function execute(): int { [$callStack] = debug_backtrace(); @@ -218,11 +306,17 @@ public function getSql(): string return $this->decorated->getSql(); } + /** + * @psalm-suppress MixedArgument + */ public function insert(string $table, QueryInterface|array $columns): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedInferredReturnType, MixedReturnStatement + */ public function insertWithReturningPks(string $table, array $columns): bool|array { return $this->decorated->{__FUNCTION__}(...func_get_args()); @@ -233,6 +327,9 @@ public function prepare(bool $forRead = null): void $this->decorated->{__FUNCTION__}(...func_get_args()); } + /** + * @psalm-suppress PossiblyUndefinedArrayOffset + */ public function query(): DataReaderInterface { [$callStack] = debug_backtrace(); @@ -250,6 +347,9 @@ public function query(): DataReaderInterface return $result; } + /** + * @psalm-suppress PossiblyUndefinedArrayOffset + */ public function queryAll(): array { [$callStack] = debug_backtrace(); @@ -266,11 +366,9 @@ public function queryAll(): array return $result; } - public function queryBuilder(): QueryBuilderInterface - { - return $this->decorated->{__FUNCTION__}(...func_get_args()); - } - + /** + * @psalm-suppress PossiblyUndefinedArrayOffset + */ public function queryColumn(): array { [$callStack] = debug_backtrace(); @@ -287,6 +385,9 @@ public function queryColumn(): array return $result; } + /** + * @psalm-suppress PossiblyUndefinedArrayOffset + */ public function queryOne(): array|null { [$callStack] = debug_backtrace(); @@ -303,6 +404,9 @@ public function queryOne(): array|null return $result; } + /** + * @psalm-suppress PossiblyUndefinedArrayOffset + */ public function queryScalar(): bool|string|null|int|float { [$callStack] = debug_backtrace(); @@ -319,51 +423,73 @@ public function queryScalar(): bool|string|null|int|float return $result; } + /** + * @psalm-suppress MixedArgument + */ public function renameColumn(string $table, string $oldName, string $newName): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function renameTable(string $table, string $newName): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function resetSequence(string $table, int|string $value = null): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } - public function setProfiler(?ProfilerInterface $profiler): void - { - $this->decorated->{__FUNCTION__}(...func_get_args()); - } - + /** + * @psalm-suppress MixedArgument + */ public function setRawSql(string $sql): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function setRetryHandler(?Closure $handler): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function setSql(string $sql): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function truncateTable(string $table): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function update(string $table, array $columns, array|string $condition = '', array $params = []): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ public function upsert( string $table, QueryInterface|array $insertColumns, @@ -393,4 +519,9 @@ private function collectQueryEnd(string $id, int $rowsNumber): void { $this->collector->collectQueryEnd($id, $rowsNumber); } + + public function showDatabases(): array + { + return $this->decorated->showDatabases(); + } } diff --git a/src/Debug/ConnectionInterfaceProxy.php b/src/Debug/ConnectionInterfaceProxy.php index e334a14f5..3944c3f6f 100644 --- a/src/Debug/ConnectionInterfaceProxy.php +++ b/src/Debug/ConnectionInterfaceProxy.php @@ -7,7 +7,6 @@ use Closure; use Yiisoft\Db\Command\CommandInterface; use Yiisoft\Db\Connection\ConnectionInterface; -use Yiisoft\Db\Profiler\ProfilerInterface; use Yiisoft\Db\Query\BatchQueryResultInterface; use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; @@ -24,6 +23,9 @@ public function __construct( ) { } + /** + * @psalm-suppress PossiblyUndefinedArrayOffset + */ public function beginTransaction(string $isolationLevel = null): TransactionInterface { [$callStack] = debug_backtrace(); @@ -60,16 +62,6 @@ public function close(): void $this->connection->close(); } - public function getCacheKey(): array - { - return $this->connection->getCacheKey(); - } - - public function getName(): string - { - return $this->connection->getName(); - } - public function getLastInsertID(string $sequenceName = null): string { return $this->connection->getLastInsertID($sequenceName); @@ -147,17 +139,21 @@ public function setTablePrefix(string $value): void $this->connection->setTablePrefix($value); } + /** + * @psalm-param Closure(self): mixed $closure + * @psalm-suppress PossiblyUndefinedArrayOffset + */ public function transaction(Closure $closure, string $isolationLevel = null): mixed { [$callStack] = debug_backtrace(); $this->collector->collectTransactionStart($isolationLevel, $callStack['file'] . ':' . $callStack['line']); - return $this->connection->transaction(fn () => $closure($this), $isolationLevel); + return $this->connection->transaction(fn (): mixed => $closure($this), $isolationLevel); } - public function setProfiler(?ProfilerInterface $profiler): void + public function getDriverName(): string { - $this->connection->setProfiler($profiler); + return $this->connection->getDriverName(); } } diff --git a/src/Debug/DatabaseCollector.php b/src/Debug/DatabaseCollector.php index 5b618b271..ff708dbb4 100644 --- a/src/Debug/DatabaseCollector.php +++ b/src/Debug/DatabaseCollector.php @@ -28,12 +28,38 @@ final class DatabaseCollector implements SummaryCollectorInterface private const QUERY_STATUS_ERROR = 'error'; private const QUERY_STATUS_SUCCESS = 'success'; + /** + * @psalm-var array + * }> + */ private array $queries = []; + /** + * @psalm-var array, + * exception: Throwable|null, + * }> + */ private array $transactions = []; private int $position = 0; private int $currentTransactionId = 0; + /** + * @psalm-suppress InvalidPropertyAssignmentValue + */ public function collectQueryStart( string $id, string $sql, @@ -56,6 +82,9 @@ public function collectQueryStart( ]; } + /** + * @psalm-suppress InvalidPropertyAssignmentValue + */ public function collectQueryEnd( string $id, int $rowsNumber, @@ -68,6 +97,9 @@ public function collectQueryEnd( ]; } + /** + * @psalm-suppress InvalidPropertyAssignmentValue + */ public function collectQueryError( string $id, Throwable $exception, @@ -80,6 +112,9 @@ public function collectQueryError( ]; } + /** + * @psalm-suppress InvalidPropertyAssignmentValue + */ public function collectTransactionStart( ?string $isolationLevel, string $line, @@ -98,9 +133,12 @@ public function collectTransactionStart( ]; } + /** + * @psalm-suppress InvalidPropertyAssignmentValue + */ public function collectTransactionRollback( string $line, - ) { + ): void { $this->transactions[$this->currentTransactionId]['status'] = self::TRANSACTION_STATUS_ROLLBACK; $this->transactions[$this->currentTransactionId]['actions'] = [ 'action' => self::ACTION_TRANSACTION_ROLLBACK, @@ -110,9 +148,12 @@ public function collectTransactionRollback( ++$this->currentTransactionId; } + /** + * @psalm-suppress InvalidPropertyAssignmentValue + */ public function collectTransactionCommit( string $line, - ) { + ): void { $this->transactions[$this->currentTransactionId]['status'] = self::TRANSACTION_STATUS_COMMIT; $this->transactions[$this->currentTransactionId]['actions'] = [ 'action' => self::ACTION_TRANSACTION_COMMIT, @@ -124,11 +165,12 @@ public function collectTransactionCommit( public function getCollected(): array { - usort($this->queries, fn (array $a, array $b) => $a['position'] <=> $b['position']); + $queries = array_values($this->queries); + usort($queries, fn (array $a, array $b) => $a['position'] <=> $b['position']); return [ - 'queries' => array_values($this->queries), - 'transactions' => array_values($this->transactions), + 'queries' => $this->queries, + 'transactions' => $this->transactions, ]; } @@ -158,5 +200,8 @@ public function getSummary(): array private function reset(): void { $this->queries = []; + $this->transactions = []; + $this->position = 0; + $this->currentTransactionId = 0; } } diff --git a/src/Debug/TransactionInterfaceDecorator.php b/src/Debug/TransactionInterfaceDecorator.php index 42707499f..80e144c9b 100644 --- a/src/Debug/TransactionInterfaceDecorator.php +++ b/src/Debug/TransactionInterfaceDecorator.php @@ -4,7 +4,6 @@ namespace Yiisoft\Db\Debug; -use Psr\Log\LoggerInterface; use Yiisoft\Db\Transaction\TransactionInterface; final class TransactionInterfaceDecorator implements TransactionInterface @@ -15,11 +14,9 @@ public function __construct( ) { } - public function setLogger(LoggerInterface $logger): void - { - $this->decorated->{__FUNCTION__}(...func_get_args()); - } - + /** + * @psalm-suppress PossiblyUndefinedArrayOffset + */ public function begin(string $isolationLevel = null): void { [$callStack] = debug_backtrace(); @@ -29,6 +26,9 @@ public function begin(string $isolationLevel = null): void $this->decorated->begin($isolationLevel); } + /** + * @psalm-suppress PossiblyUndefinedArrayOffset + */ public function commit(): void { [$callStack] = debug_backtrace(); @@ -48,6 +48,9 @@ public function isActive(): bool return $this->decorated->isActive(); } + /** + * @psalm-suppress PossiblyUndefinedArrayOffset + */ public function rollBack(): void { [$callStack] = debug_backtrace(); From 2553d3b9a42c72d8cb7f8123550c4fbb282ebd3e Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 29 May 2023 19:54:08 +0300 Subject: [PATCH 10/11] Trigger ci --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index aa7bc525e..13b7d236d 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "rector/rector": "^0.14", "roave/infection-static-analysis-plugin": "^1.16", "spatie/phpunit-watcher": "^1.23", - "vimeo/psalm": "^4.30|^5.6", + "vimeo/psalm": "^4.30|^5.12.0", "yiisoft/aliases": "^3.0", "yiisoft/cache-file": "^2.0", "yiisoft/di": "^1.0", From 81ba449c2f6131ffdcb67c3b72aeda92b306147b Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Fri, 28 Jul 2023 21:15:39 +0300 Subject: [PATCH 11/11] Disallow config plugin --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c77dff78a..1bb3b8b72 100644 --- a/composer.json +++ b/composer.json @@ -72,7 +72,8 @@ "sort-packages": true, "allow-plugins": { "infection/extension-installer": true, - "composer/package-versions-deprecated": true + "composer/package-versions-deprecated": true, + "yiisoft/config": false } }, "scripts": {