From dbcbeda8285924a3da3d7fb94989405ad931207c Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 27 Mar 2023 00:27:49 +0300 Subject: [PATCH] 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()); + } +}