Commit 7ce4df95 authored by abkrim's avatar abkrim 😀
Browse files

First version with doctrina/dbal and new commands

parent bf6b20a5
Pipeline #227 failed with stages
in 52 seconds
......@@ -3,9 +3,15 @@
All notable changes to `db-schema` will be documented in this file
## 0.9.0 - 2019-09-09
### Added
- initial release for Laravel 6 without tests. ALl commands tested in brute. Several changes over original for compatibilty with Laravel 6
## 0.9.1 - 2019-09-09
### Added
- Correct some errors with namespace and facade declarations
## [1.0.0] - 2019-09-10
### Added
- Create a new class DoctrineListSchema for command db-schema:doctrine-list. This is using Doctrine / DBAL to not depend on different database models. In principle and because of the urgency of my other projects I have not performed testing and I have only tested it on MySQL, but in principle it should work with the rest of the Database engines.
- This command permit save schemas in tables for control changes in databases of our project or databases associated with our project but not maintained by us.
......@@ -27,6 +27,18 @@ php artisan vendor:publish --tag=config-db-schema
## Usage
**View or save a connection with version and information of each table**
New command for see or show schema and indexes for any connection fo our Laravel project, using Doctrine instead use several wrappers.
Only tested with MySQL (not testing phpunit, testing in production) but is possible that work fine with Postgresql, sqlite,...
```bash
php artisan db-schema:doctrine-list
```
![Schema and indexes information in tabular form](https://gitlab.castris.com/root/db-schema/raw/master/screenshots/db-schema-doctrine-schema.png)
See --help for more info
**Show Schema information in tabular form**
```bash
php artisan db-schema:show
......
......@@ -20,6 +20,7 @@
"require": {
"php": "^7.2",
"laravel/framework": "^6.0",
"doctrine/dbal": "^2.9",
"ext-json": "*"
},
"require-dev": {
......
<?php
/*
* You can place your custom package configuration in here.
*/
return [
'table' => 'db_schemas'
];
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateDbSchemasTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('db_schemas', function (Blueprint $table) {
$table->increments('id');
$table->string('connection',100)->nullable(false)->index();
$table->string('table',100)->nullable(false)->index();
$table->string('version', 25)->nullable(false)->index();
$table->text('schema');
$table->timestamps();
$table->unique(['connection', 'table', 'version']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('db_schemas');
}
}
<?php
namespace Abkrim\DbSchema\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Abkrim\DbSchema\Schema\Helper;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Mockery\Exception;
use Symfony\Component\Console\Input\InputOption;
class DoctrineListSchema extends Command
{
use Helper;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $name = 'db-schema:doctrine-list';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Display connected database db-schema information in list using Doctrine';
private $table_name;
private $connection;
private $db_tables = array();
public function handle()
{
//change connection if provide
$this->setConnection();
// Get all tables
$this->setDbTables();
if (!count($this->db_tables)) {
$this->warn('Database does not contain any table');
exit();
}
if (is_null($this->option('t'))) {
$this->info('Show all tables on '.$this->connection);
$tables = $this->db_tables;
foreach ($tables as $table) {
$this->getSchemaTable($table);
}
} else {
$this->getSchemaTable($this->option('t'));
}
}
private function getSchemaTable(string $table)
{
$table_schemas = array();
$sm = Schema::connection('mysql_vmail')->getConnection()->getDoctrineSchemaManager();
$indexes = $sm->listTableIndexes($table);
foreach ($indexes as $index) {
$table_schemas['indexes'][] = [
'name' => $index->getName(),
'unique' => $index->isUnique(),
'columns' => implode(',', $index->getColumns())
];
}
// Now get columns
$columns = $sm->listTableColumns($table);
foreach ($columns as $column) {
$table_schemas['columns'][] = [
'name' => $column->getName(),
'type' => str_replace("Doctrine\DBAL\Types\\", "", $column->getType()),
'length' => $column->getLength(),
'precision' => $column->getPrecision(),
'unsigned' => $column->getUnsigned(),
'notnull' => $column->getNotNull(),
'default' => $column->getDefault(),
'autoincrement' => $column->getAutoincrement(),
'options' => implode(',', $column->getPlatformOptions())
];
}
//dd($this->option('e'));
if ($this->option('s') == true ) {
if (! is_null($this->option('e'))) {
$this->saveSchema($table, $table_schemas);
} else {
$this->warn('If use otpion "s" you must use option "-e" with version for save');
}
} else {
$this->showSchema($table, $table_schemas);
}
}
private function showSchema(string $table, array $table_schemas): void
{
$this->info("Table: ". $table);
// Columns
$rows = $table_schemas['columns'];
$headers = (array_keys($rows[0]));
$this->table($headers, $rows);
$this->info("Indexes: ". $table);
// Indexes
$rows = $table_schemas['indexes'];
$headers = (array_keys($rows[0]));
$this->table($headers, $rows);
}
private function saveSchema(string $table, array $table_schemas): void
{
if ($this->option('f')) {
$this->saveOrUpdateTableSchema($table, $table_schemas);
} else {
$this->saveTableSchema($table, $table_schemas);
}
}
private function setConnection(): void
{
if ($this->option('c')) {
$this->connection = $this->option('c');
// Verify if connection exists
if (! Arr::has(config('database.connections'), $this->connection)) {
$this->error('Incorrect option database connection');
exit;
}
}
}
private function setDbTables(): void
{
$sm_tables = Schema::connection($this->connection)->getAllTables();
$sm_db = 'Tables_in_'.config('database.connections.' . $this->connection . '.database');
foreach ($sm_tables as $sm_table) {
$this->db_tables[] = $sm_table->$sm_db;
}
}
protected function getOptions()
{
return [
['t', 't', InputOption::VALUE_OPTIONAL, 'Table name'],
['c', 'c', InputOption::VALUE_OPTIONAL, 'Connection name'],
['e', 'e', InputOption::VALUE_OPTIONAL, 'Version'],
['s', 's', InputOption::VALUE_NONE, 'Save schema version'],
['f', 'f', InputOption::VALUE_NONE, 'Force save schema']
];
}
/**
* @param string $table
* @param array $table_schemas
*/
private function saveTableSchema(string $table, array $table_schemas): void
{
$now = Carbon::now();
try {
DB::table(config('db-schema.table'))->insert([
'connection' => config('database.connections.' . $this->connection . '.database'),
'table' => $table,
'version' => $this->option('e'),
'schema' => json_encode($table_schemas),
'created_at' => $now,
'updated_at' => $now
]);
} catch (\Exception $e) {
$this->warn("Some connection+table+version is already in system");
echo $e->getMessage(); //Integrity constraint violation:
}
}
private function saveOrUpdateTableSchema(string $table, array $table_schemas): void
{
$now = Carbon::now();
DB::table(config('db-schema.table'))->updateOrInsert(
[
'connection' => config('database.connections.' . $this->connection . '.database'),
'table' => $table,
'version' => $this->option('e')
],
[
'schema' => json_encode($table_schemas),
'updated_at' => $now
]
);
}
}
......@@ -26,6 +26,8 @@ class ListSchema extends Command
private $dbschema;
private $db_tables;
public function __construct(DbSchema $dbschema)
{
parent::__construct();
......@@ -42,6 +44,17 @@ class ListSchema extends Command
public function handle()
{
$this->showSchemaInList();
//dd($this->options());
// if ($this->option('s')) {
// $this->saveSchemaList();
// } else {
// $this->getSchemaTable();
// }
}
private function saveSchemaList()
{
$this->info('Save schema');
}
/**
......@@ -51,17 +64,46 @@ class ListSchema extends Command
public function showSchemaInList()
{
//change connection if provide
$this->setConnection();
$this->setDbTables();
if (!count($this->db_tables)) {
$this->warn('Database does not contain any table');
exit();
}
if (!is_null($this->option('t'))) {
$this->showSchemaTableInList();
}
$this->getFullDbSchema();
}
protected function getOptions()
{
return [
['t', 't', InputOption::VALUE_OPTIONAL, 'Table name'],
['c', 'c', InputOption::VALUE_OPTIONAL, 'Connection name'],
['s', 's', InputOption::VALUE_NONE, 'Save schema version'],
['f', 'f', InputOption::VALUE_NONE, 'Force save schema']
];
}
private function setConnection(): void
{
if ($this->option('c')) {
$this->dbschema->setConnection($this->option('c'));
$this->dbschema->switchWrapper();
}
}
private function showSchemaTableInList()
{
$tables = $this->dbschema->databaseWrapper->getTables();
if (!count($tables)) {
$this->warn('Database does not contain any table');
}
$tableName = $this->option('t');
if (!empty($tableName)) {
if ($this->isNamespaceModel($tableName)) {
$tableName = $this->tableNameFromModel($tableName);
......@@ -77,23 +119,36 @@ class ListSchema extends Command
foreach ($attributes as $attribute) {
$this->line(' ' . $attribute['Field'] . ' ' . $attribute['Type']);
}
return true;
exit();
}
}
private function setDbTables(): void
{
$this->db_tables = $this->dbschema->databaseWrapper->getTables();
/*
$tables = $this->baseDbSchema->database->select('SHOW TABLES');
$attribute = 'Tables_in_' . $this->baseDbSchema->getDatabaseName();
return array_map(function ($table) use ($attribute) {
return $table->$attribute;
}, $tables);
*/
}
private function getFullDbSchema(): void
{
$isSave = $this->option('s');
foreach ($this->dbschema->databaseWrapper->getSchema() as $key => $value) {
$this->info($key . ' (rows: ' . $value['rowsCount'] . ')');
// $key name of table
foreach ($value['attributes'] as $attribute) {
$this->line(' ' . $attribute['Field'] . ' ' . $attribute['Type']);
//dd($attribute);
}
$this->line('');
//exit();
}
}
protected function getOptions()
{
return [
['t', 't', InputOption::VALUE_OPTIONAL, 'Table name'],
['c', 'c', InputOption::VALUE_OPTIONAL, 'Connection name'],
];
}
}
......@@ -130,6 +130,7 @@ class TableSchema extends Command
}
$rows = $this->dbschema->getPaginatedData($tableName, $page, $limit, $attributeName, $order)['data'];
//("Rows",$headers, $rows);
$body = $this->makeTableBody($headers, $rows);
$rowsCount = $this->dbschema->getTableRowCount($tableName);
......
......@@ -2,6 +2,7 @@
namespace Abkrim\DbSchema;
use Abkrim\DbSchema\Console\Commands\DoctrineListSchema;
use Abkrim\DbSchema\Console\Commands\HelpSchema;
use Abkrim\DbSchema\Console\Commands\ListSchema;
use Abkrim\DbSchema\Console\Commands\MonitorSchema;
......@@ -22,15 +23,20 @@ class DbSchemaServiceProvider extends ServiceProvider
$this->commands([
ShowSchema::class,
ListSchema::class,
HelpSchema::class,
MonitorSchema::class,
QuerySchema::class,
SimpleSchema::class,
TableSchema::class
]);
// $this->publishes([
// __DIR__.'/../config/config.php' => config_path('db-schema.php'),
// ], 'db-schema-config');
$this->publishes([
__DIR__.'/../config/db-schema.php' => config_path('db-schema.php'),
], 'db-schema-config');
// $this->publishes([
// __DIR__.'/../database/migrations' => database_path('migrations'),
// ], 'db-schema-migrations');
$this->publishes([
__DIR__.'/../database/migrations' => database_path('migrations'),
], 'db-schema-migrations');
$this->commands([
......@@ -40,7 +46,8 @@ class DbSchemaServiceProvider extends ServiceProvider
QuerySchema::class,
ShowSchema::class,
SimpleSchema::class,
TableSchema::class
TableSchema::class,
DoctrineListSchema::class
]);
}
}
......
......@@ -4,6 +4,9 @@ namespace Abkrim\DbSchema\Schema\Wrapper;
use Abkrim\DbSchema\Schema\Helper;
use Abkrim\DbSchema\Schema\BaseDbSchema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class MySQLWrapper implements WrapperContract
{
......@@ -34,6 +37,17 @@ class MySQLWrapper implements WrapperContract
{
foreach ($this->getTables() as $table) {
$columns = $this->getColumns($table);
// Schema::table($table, function (Blueprint $dbtable) use ($table) {
// $sm = Schema::getConnection()->getDoctrineSchemaManager();
// //dd($table, $sm->listTableIndexes($table));
// echo "Table: " . $table.PHP_EOL;
// $indexesFound = $sm->listTableIndexes($table);
// foreach ($indexesFound as $index) {
// echo $index->getName() . ': ' . implode(',', $index->getColumns()) .' '. ($index->isUnique() ? 'unique' : 'not unique') . "\n";
// }
// });
$this->dbSchema[$table]['attributes'] = $columns;
$this->dbSchema[$table]['rowsCount'] = $this->baseDbSchema->getTableRowCount($table);
}
......
......@@ -6,6 +6,7 @@
"Monitor": "Display current database server performance status.\n 1. --c=connectionName\n 2. --i=interval time for refreshing\n",
"Query": "Perform raw query.\n 1. --c=connectionName\n 2. --r=yourRawSqlQuery\n",
"Show": "Display all the available tables in a tabular form.\nAvailable Options:.\n 1. --c=connectionName\n 2. --t=tableName or Namepace\\Model\n",
"Table": "Display table paginated rows.\nAvailable Options:\n 1. --c=connectionName\n 2. --t=tableName or Namepace\\Model\n 3. --p=pageNumber(numeric [default=1])\n 4. --l=limitOfRowsPerPage(numeric [default=15])\n 5. --o=orderBy (e.g: --o=id:desc or --o=id:asc [default=asc])\n 6. --w=widthOfCell(char length in numeric [default = 10])\n 7. --s=select columns to display, use comma (,) to separated column names\n\nNote:\n * For all commands you can provide --option=value or -option value\n * --c=connectionName is always optional [default=defaultConnectionName].\n"
"Table": "Display table paginated rows.\nAvailable Options:\n 1. --c=connectionName\n 2. --t=tableName or Namepace\\Model\n 3. --p=pageNumber(numeric [default=1])\n 4. --l=limitOfRowsPerPage(numeric [default=15])\n 5. --o=orderBy (e.g: --o=id:desc or --o=id:asc [default=asc])\n 6. --w=widthOfCell(char length in numeric [default = 10])\n 7. --s=select columns to display, use comma (,) to separated column names\n\nNote:\n * For all commands you can provide --option=value or -option value\n * --c=connectionName is always optional [default=defaultConnectionName].\n",
"DoctrineList": "View or save a connection with version and information of each table.\nAvailable Options:\n 1. --c=connectionName\n 2. --t=tableName\n 3. --e=version Of connection\n 4. --s=Save schema in table\n 5. Force save schema\n"
}
}
......@@ -29,8 +29,10 @@ class DbSchemaTest extends TestCase
protected function getEnvironmentSetUp($app)
{
include_once __DIR__ . '/../database/migrations_test/create_users_table.php.stub';
include_once __DIR__ . '/../database/migrations/2019_09_09_000000_create_db_schemas_table.php';
(new \CreateUsersTable)->up();
(new \CreateDbSchemasTable)->up();
}
/** @test */
......@@ -50,4 +52,25 @@ class DbSchemaTest extends TestCase
$this->assertSame($user->email, $mail);
}
/** @test */
function a_table_must_be_unique_by_connection_and_version()
{
$this->expectException(QueryException::class);
DB::table('db_schemas')->insert([
[
'connection' => 'mysql',
'table' => 'table1',
'version' => '0.9.1'
],
[
'connection' => 'mysql',
'table' => 'table1',
'version' => '0.9.1'
],
]);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment