Skip to content

Commit

Permalink
Database (#68)
Browse files Browse the repository at this point in the history
* Add database inspection

* Apply fixes from StyleCI

* [ci-review] Apply changes from Rector action.

* Support Cycle

* Remove var

* Add dependencies to dev section

* Add abstraction for db

* Apply fixes from StyleCI

* Fix deps check

Co-authored-by: StyleCI Bot <bot@styleci.io>
Co-authored-by: rector-bot <rector@yiiframework.com>
  • Loading branch information
3 people committed Dec 5, 2022
1 parent bb8b098 commit bad803e
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 3 deletions.
6 changes: 5 additions & 1 deletion composer-require-checker.json
Expand Up @@ -16,6 +16,10 @@
"PHPUnit\\Framework\\Warning",
"PHPUnit\\Runner\\BaseTestRunner",
"PHPUnit\\TextUI\\ResultPrinter",
"PHPUnit\\Util\\TestDox\\NamePrettifier"
"PHPUnit\\Util\\TestDox\\NamePrettifier",
"Cycle\\Database\\DatabaseProviderInterface",
"Yiisoft\\ActiveRecord\\ActiveRecord",
"Yiisoft\\ActiveRecord\\ActiveRecordFactory",
"Yiisoft\\Db\\Connection\\ConnectionInterface"
]
}
5 changes: 4 additions & 1 deletion composer.json
Expand Up @@ -50,9 +50,12 @@
"roave/infection-static-analysis-plugin": "^1.16",
"spatie/phpunit-watcher": "^1.23",
"vimeo/psalm": "^4.18",
"yiisoft/active-record": "3.0.x-dev",
"yiisoft/csrf": "^1.2",
"yiisoft/db": "3.0.x-dev",
"yiisoft/psr-dummy-provider": "^1.0",
"yiisoft/router-fastroute": "^2.0"
"yiisoft/router-fastroute": "^2.0",
"yiisoft/yii-cycle": "3.0.x-dev"
},
"autoload": {
"psr-4": {
Expand Down
6 changes: 6 additions & 0 deletions config/routes.php
Expand Up @@ -97,6 +97,12 @@ static function (ResponseFactoryInterface $responseFactory, ValidatorInterface $
Route::put('/translations')
->action([InspectController::class, 'putTranslation'])
->name('putTranslation'),
Route::get('/table')
->action([InspectController::class, 'getTables'])
->name('getTables'),
Route::get('/table/{name}')
->action([InspectController::class, 'getTable'])
->name('getTable'),
Route::put('/request')
->action([InspectController::class, 'request'])
->name('request'),
Expand Down
22 changes: 22 additions & 0 deletions config/web.php
Expand Up @@ -2,6 +2,12 @@

declare(strict_types=1);

use Cycle\Database\DatabaseProviderInterface;
use Psr\Container\ContainerInterface;
use Yiisoft\Db\Connection\ConnectionInterface;
use Yiisoft\Yii\Debug\Api\Inspector\ActiveRecord\ActiveRecordSchemaProvider;
use Yiisoft\Yii\Debug\Api\Inspector\Database\Cycle\CycleSchemaProvider;
use Yiisoft\Yii\Debug\Api\Inspector\Database\SchemaProviderInterface;
use Yiisoft\Yii\Debug\Api\Repository\CollectorRepository;
use Yiisoft\Yii\Debug\Api\Repository\CollectorRepositoryInterface;
use Yiisoft\Yii\Debug\Storage\StorageInterface;
Expand All @@ -12,4 +18,20 @@

return [
CollectorRepositoryInterface::class => static fn (StorageInterface $storage) => new CollectorRepository($storage),
SchemaProviderInterface::class => function (ContainerInterface $container) {
if ($container->has(DatabaseProviderInterface::class)) {
return $container->get(CycleSchemaProvider::class);
}

if ($container->has(ConnectionInterface::class)) {
return $container->get(ActiveRecordSchemaProvider::class);
}

throw new LogicException(
sprintf(
'Inspecting database is not available. Configure "%s" service to be able to inspect database.',
ConnectionInterface::class,
)
);
},
];
18 changes: 17 additions & 1 deletion src/Controller/InspectController.php
Expand Up @@ -19,11 +19,13 @@
use Yiisoft\Aliases\Aliases;
use Yiisoft\Config\ConfigInterface;
use Yiisoft\DataResponse\DataResponseFactoryInterface;
use Yiisoft\Router\CurrentRoute;
use Yiisoft\Router\RouteCollectionInterface;
use Yiisoft\Translator\CategorySource;
use Yiisoft\VarDumper\VarDumper;
use Yiisoft\Yii\Debug\Api\Inspector\ApplicationState;
use Yiisoft\Yii\Debug\Api\Inspector\CommandInterface;
use Yiisoft\Yii\Debug\Api\Inspector\Database\SchemaProviderInterface;
use Yiisoft\Yii\Debug\Api\Repository\CollectorRepositoryInterface;
use Yiisoft\Yii\Debug\Collector\RequestCollector;

Expand Down Expand Up @@ -361,8 +363,22 @@ public function runCommand(
]);
}

public function request(ServerRequestInterface $request, CollectorRepositoryInterface $collectorRepository): ResponseInterface
public function getTables(SchemaProviderInterface $schemaProvider): ResponseInterface
{
return $this->responseFactory->createResponse($schemaProvider->getTables());
}

public function getTable(SchemaProviderInterface $schemaProvider, CurrentRoute $currentRoute): ResponseInterface
{
$tableName = $currentRoute->getArgument('name');

return $this->responseFactory->createResponse($schemaProvider->getTable($tableName));
}

public function request(
ServerRequestInterface $request,
CollectorRepositoryInterface $collectorRepository
): ResponseInterface {
$request = $request->getQueryParams();
$debugEntryId = $request['debugEntryId'] ?? null;

Expand Down
93 changes: 93 additions & 0 deletions src/Inspector/Database/ActiveRecord/ActiveRecordSchemaProvider.php
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\Api\Inspector\ActiveRecord;

use Yiisoft\ActiveRecord\ActiveRecord;
use Yiisoft\ActiveRecord\ActiveRecordFactory;
use Yiisoft\Db\Connection\ConnectionInterface;
use Yiisoft\Db\Schema\ColumnSchemaInterface;
use Yiisoft\Db\Schema\TableSchemaInterface;
use Yiisoft\Yii\Debug\Api\Inspector\Database\SchemaProviderInterface;

class ActiveRecordSchemaProvider implements SchemaProviderInterface
{
public function __construct(
private ConnectionInterface $connection,
private ActiveRecordFactory $activeRecordFactory,
) {
}

public function getTables(): array
{
/** @var TableSchemaInterface[] $tableSchemas */
$tableSchemas = $this->connection->getSchema()->getTableSchemas();

$tables = [];
foreach ($tableSchemas as $schema) {
$activeQuery = $this->activeRecordFactory->createQueryTo(ActiveRecord::class, $schema->getName());

/**
* @var ActiveRecord[] $records
*/
$records = $activeQuery->count();

$tables[] = [
'table' => $schema->getName(),
'primaryKeys' => $schema->getPrimaryKey(),
'columns' => $this->serializeARColumnsSchemas($schema->getColumns()),
'records' => $records,
];
}
return $tables;
}

public function getTable(string $tableName): array
{
/** @var TableSchemaInterface[] $tableSchemas */
$schema = $this->connection->getSchema()->getTableSchema($tableName);

$activeQuery = $this->activeRecordFactory->createQueryTo(ActiveRecord::class, $tableName);

/**
* @var ActiveRecord[] $records
*/
$records = $activeQuery->all();

$data = [];
// TODO: add pagination
foreach ($records as $n => $record) {
foreach ($record->attributes() as $attribute) {
$data[$n][$attribute] = $record->{$attribute};
}
}

return [
'table' => $schema->getName(),
'primaryKeys' => $schema->getPrimaryKey(),
'columns' => $this->serializeARColumnsSchemas($schema->getColumns()),
'records' => $data,
];
}

/**
* @param ColumnSchemaInterface[] $columns
*/
private function serializeARColumnsSchemas(array $columns): array
{
$result = [];
foreach ($columns as $columnSchema) {
$result[] = [
'name' => $columnSchema->getName(),
'size' => $columnSchema->getSize(),
'type' => $columnSchema->getType(),
'dbType' => $columnSchema->getDbType(),
'defaultValue' => $columnSchema->getDefaultValue(),
'comment' => $columnSchema->getComment(),
'allowNull' => $columnSchema->isAllowNull(),
];
}
return $result;
}
}
71 changes: 71 additions & 0 deletions src/Inspector/Database/Cycle/CycleSchemaProvider.php
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\Api\Inspector\Database\Cycle;

use Cycle\Database\ColumnInterface;
use Cycle\Database\DatabaseProviderInterface;
use Yiisoft\Yii\Debug\Api\Inspector\Database\SchemaProviderInterface;

class CycleSchemaProvider implements SchemaProviderInterface
{
public function __construct(private DatabaseProviderInterface $databaseProvider)
{
}

public function getTables(): array
{
$database = $this->databaseProvider->database();
$tableSchemas = $database->getTables();

$tables = [];
foreach ($tableSchemas as $schema) {
$records = $database->select()->from($schema->getName())->count();
$tables[] = [
'table' => $schema->getName(),
'primaryKeys' => $schema->getPrimaryKeys(),
'columns' => $this->serializeCycleColumnsSchemas($schema->getColumns()),
'records' => $records,
];
}

return $tables;
}

public function getTable(string $tableName): array
{
$database = $this->databaseProvider->database();
$schema = $database->table($tableName);

// TODO: add pagination
$records = $database->select()->from($tableName)->fetchAll();

return [
'table' => $schema->getName(),
'primaryKeys' => $schema->getPrimaryKeys(),
'columns' => $this->serializeCycleColumnsSchemas($schema->getColumns()),
'records' => $records,
];
}

/**
* @param ColumnInterface[] $columns
*/
private function serializeCycleColumnsSchemas(array $columns): array
{
$result = [];
foreach ($columns as $columnSchema) {
$result[] = [
'name' => $columnSchema->getName(),
'size' => $columnSchema->getSize(),
'type' => $columnSchema->getInternalType(),
'dbType' => $columnSchema->getType(),
'defaultValue' => $columnSchema->getDefaultValue(),
'comment' => null, // unsupported for now
'allowNull' => $columnSchema->isNullable(),
];
}
return $result;
}
}
12 changes: 12 additions & 0 deletions src/Inspector/Database/SchemaProviderInterface.php
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\Api\Inspector\Database;

interface SchemaProviderInterface
{
public function getTables(): array;

public function getTable(string $tableName): array;
}

0 comments on commit bad803e

Please sign in to comment.