├── .env.example ├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── DocUsServiceProvider.php ├── Parser.php ├── routes.php └── views │ ├── html.blade.php │ ├── json.blade.php │ └── markdown.blade.php └── tests ├── ParserTest.php ├── TestCase.php └── bootstrap.php /.env.example: -------------------------------------------------------------------------------- 1 | DB_HOST=127.0.0.1 2 | DB_PORT=3306 3 | DB_DATABASE=homestead 4 | DB_USERNAME=homestead 5 | DB_PASSWORD=secret 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | /vendor 3 | composer.lock 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2017 UniSharp & Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Stable Version](https://poser.pugx.org/unisharp/doc-us/v/stable)](https://packagist.org/packages/unisharp/doc-us) 2 | [![Total Downloads](https://poser.pugx.org/unisharp/doc-us/downloads)](https://packagist.org/packages/unisharp/doc-us) 3 | [![License](https://poser.pugx.org/unisharp/doc-us/license)](https://packagist.org/packages/unisharp/doc-us) 4 | 5 | # Doc Us 6 | 7 | A MySQL Schema Documentation Generator for Laravel. 8 | 9 | ## Installation 10 | 11 | 1. Require this package with composer: 12 | 13 | ```bash 14 | composer require unisharp/doc-us 15 | ``` 16 | 17 | 2. Add `ENABLE_DOC_US` in `.env` (Default is disable) 18 | 19 | ``` 20 | ENABLE_DOC_US=true 21 | ``` 22 | 23 | 3. Add the ServiceProvider to the providers array in `config/app.php`: 24 | 25 | > If you are using Laravel 5.5 or newer, you don’t need to do this step. 26 | 27 | ```php 28 | 'providers' => [ 29 | /* ... */ 30 | 31 | UniSharp\DocUs\DocUsServiceProvider::class, 32 | 33 | /* ... */ 34 | ]; 35 | ``` 36 | 37 | ## Usage 38 | 39 | ### Output format 40 | 41 | 42 | 43 | Supported Formats 44 | 45 | - html 46 | - markdown 47 | - json 48 | 49 | ### exclude special table 50 | 51 | 52 | 53 | Using comma to separate multiple table. 54 | 55 | like 56 | 57 | 58 | 59 | ## Demo 60 | 61 | #### HTML 62 | 63 | ![html](http://i.imgur.com/EQaDRXMg.png) 64 | 65 | #### Markdown 66 | 67 | ![markdown](http://i.imgur.com/kt92Uflg.png) 68 | 69 | #### Json 70 | 71 | ![json](http://i.imgur.com/VCzAw3Qg.png) 72 | 73 | ## Test 74 | 75 | ``` 76 | vendor/bin/phpunit tests 77 | ``` 78 | 79 | ## License 80 | 81 | The DocUs released under [MIT license](https://unisharp.mit-license.org/). 82 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unisharp/doc-us", 3 | "description": "A MySQL Schema Documantation Generator for Laravel", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "UniSharp Ltd.", 8 | "email": "unisharp-service@unisharp.com", 9 | "homepage": "http://unisharp.com" 10 | } 11 | ], 12 | "require": { 13 | "illuminate/database": ">5.3", 14 | "illuminate/support": ">5.3" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "UniSharp\\DocUs\\": "src" 19 | } 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "^5.6", 23 | "mockery/mockery": "^0.9.6", 24 | "vlucas/phpdotenv": "^2.4" 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "Tests\\": "tests" 29 | } 30 | }, 31 | "extra": { 32 | "laravel": { 33 | "providers": [ 34 | "UniSharp\\DocUs\\DocUsServiceProvider" 35 | ] 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/DocUsServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadRoutesFrom(__DIR__ . '/routes.php'); 13 | 14 | $this->loadViewsFrom(__DIR__ . '/views', 'schema'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Parser.php: -------------------------------------------------------------------------------- 1 | map(function ($row) { 13 | return head($row); 14 | })->filter(function ($table) use ($exclude) { 15 | return !in_array($table, $exclude); 16 | })->map(function ($table) { 17 | return [ 18 | 'name' => $table, 19 | 'columns' => static::getColumns($table), 20 | 'indices' => static::getIndices($table), 21 | ]; 22 | }); 23 | } 24 | 25 | protected static function getColumns($table) 26 | { 27 | $sql = ((array) head(DB::select("show create table `{$table}`")))['Create Table']; 28 | 29 | return collect(DB::select("show columns from `{$table}`"))->map(function ($column) use ($sql) { 30 | $attributes = collect(); 31 | 32 | if ('PRI' === $column->Key) { 33 | $attributes->push('Primary'); 34 | } 35 | 36 | if (Str::contains($column->Extra, 'auto_increment')) { 37 | $attributes->push('Auto increment'); 38 | } 39 | 40 | if ('NO' === $column->Null) { 41 | $attributes->push('Not null'); 42 | } 43 | 44 | preg_match("/`{$column->Field}`.+?COMMENT '(.+)'/", $sql, $matches); 45 | 46 | return (object) [ 47 | 'name' => $column->Field, 48 | 'type' => strtoupper($column->Type), 49 | 'attributes' => $attributes, 50 | 'default' => $column->Default ?: ('YES' === $column->Null ? 'NULL' : ''), 51 | 'description' => count($matches) ? $matches[1] : '', 52 | ]; 53 | }); 54 | } 55 | 56 | protected static function getIndices($table) 57 | { 58 | return collect(DB::select("show index from `{$table}`")) 59 | ->groupBy('Key_name') 60 | ->map(function ($indices) { 61 | switch (true) { 62 | case 'PRIMARY' === $indices->first()->Key_name: 63 | $type = 'PRIMARY'; 64 | break; 65 | 66 | case 0 === $indices->first()->Non_unique: 67 | $type = 'UNIQUE'; 68 | break; 69 | 70 | default: 71 | $type = 'INDEX'; 72 | break; 73 | } 74 | 75 | return (object) [ 76 | 'name' => $indices->first()->Key_name, 77 | 'columns' => $indices->pluck('Column_name'), 78 | 'type' => $type, 79 | 'description' => '', 80 | ]; 81 | }) 82 | ->values(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/routes.php: -------------------------------------------------------------------------------- 1 | exclude !== null) { 7 | $exclude = explode(',', $request->exclude); 8 | } 9 | 10 | $schema = UniSharp\DocUs\Parser::getSchema($exclude); 11 | 12 | $supportedFormats = array_map(function ($path) { 13 | return head(explode('.', basename($path))); 14 | }, Illuminate\Support\Facades\File::files(__DIR__ . '/views')); 15 | 16 | if (!in_array($format = strtolower($request->format), $supportedFormats)) { 17 | $format = 'html'; 18 | } 19 | 20 | return response()->view("schema::{$format}", compact('schema')) 21 | ->header('Content-Type', "text/{$format}"); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/views/html.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Schema Documentation

14 | @foreach ($schema as $table) 15 |

Table: {{ $table['name'] }}

16 |
Columns
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | @foreach ($table['columns'] as $column) 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | @endforeach 34 |
ColumnData TypeAttributesDefaultDescription
{{ $column->name }}{{ $column->type }}{{ $column->attributes->implode(', ') }}{{ $column->default }}{{ $column->description }}
35 | @if (count($table['indices'])) 36 |
Indices
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | @foreach($table['indices'] as $indices) 45 | 46 | 47 | 48 | 49 | 50 | 51 | @endforeach 52 |
NameColumnsTypeDescription
{{ $indices->name }}{{ $indices->columns->implode(', ') }}{{ $indices->type }}{{ $indices->description }}
53 | @endif 54 | @endforeach 55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /src/views/json.blade.php: -------------------------------------------------------------------------------- 1 | @if(request()->has('pretty') && request('pretty')) 2 | {!! json_encode($schema, JSON_PRETTY_PRINT) !!} 3 | @else 4 | {!! $schema->toJson() !!} 5 | @endif 6 | -------------------------------------------------------------------------------- /src/views/markdown.blade.php: -------------------------------------------------------------------------------- 1 | # Schema Documentation 2 | @foreach ($schema as $table) 3 | 4 | ## Table: `{{ $table['name'] }}` 5 | 6 | ### Description: 7 | 8 | 9 | 10 | ### Columns: 11 | 12 | | Column | Data Type | Attributes | Default | Description | 13 | | --- | --- | --- | --- | --- | 14 | @foreach ($table['columns'] as $column) 15 | | `{{ $column->name }}` | {{ $column->type }} | {{ $column->attributes->implode(', ') }} | {{ $column->default }} | {{ $column->description }} | 16 | @endforeach 17 | @if (count($table['indices'])) 18 | 19 | ### Indices: 20 | 21 | | Name | Columns | Type | Description | 22 | | --- | --- | --- | --- | 23 | @foreach($table['indices'] as $indices) 24 | | `{{ $indices->name }}` | {{ $indices->columns->map(function ($column) { return "`{$column}`"; })->implode(', ') }} | {{ $indices->type }} | {{ $indices->description }} | 25 | @endforeach 26 | @endif 27 | @endforeach 28 | -------------------------------------------------------------------------------- /tests/ParserTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('tests', $schema[0]['name']); 28 | 29 | DB::statement('DROP TABLE `tests`'); 30 | } 31 | 32 | public function testColumn() 33 | { 34 | DB::statement( 35 | "CREATE TABLE `tests` ( 36 | `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'comment', 37 | PRIMARY KEY (`id`) 38 | )" 39 | ); 40 | 41 | $schema = Parser::getSchema(); 42 | $column = $schema[0]['columns'][0]; 43 | 44 | $this->assertEquals('id', $column->name); 45 | $this->assertEquals('INT(10) UNSIGNED', $column->type); 46 | $this->assertEquals('', $column->default); 47 | $this->assertEquals('comment', $column->description); 48 | 49 | $this->assertContains('Primary', $column->attributes); 50 | $this->assertContains('Auto increment', $column->attributes); 51 | $this->assertContains('Not null', $column->attributes); 52 | 53 | DB::statement('DROP TABLE `tests`'); 54 | } 55 | 56 | public function testNull() 57 | { 58 | DB::statement( 59 | 'CREATE TABLE `tests` ( 60 | `test` VARCHAR(1) NULL 61 | )' 62 | ); 63 | 64 | $schema = Parser::getSchema(); 65 | $column = $schema[0]['columns'][0]; 66 | 67 | $this->assertEquals('NULL', $column->default); 68 | 69 | DB::statement('DROP TABLE `tests`'); 70 | } 71 | 72 | public function testDefault() 73 | { 74 | DB::statement( 75 | "CREATE TABLE `tests` ( 76 | `test` VARCHAR(10) NULL DEFAULT 'default' 77 | )" 78 | ); 79 | 80 | $schema = Parser::getSchema(); 81 | $column = $schema[0]['columns'][0]; 82 | 83 | $this->assertEquals('default', $column->default); 84 | 85 | DB::statement('DROP TABLE `tests`'); 86 | } 87 | 88 | public function testPrimaryKey() 89 | { 90 | DB::statement( 91 | "CREATE TABLE `tests` ( 92 | `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, 93 | PRIMARY KEY (`id`) 94 | )" 95 | ); 96 | 97 | $schema = Parser::getSchema(); 98 | $indices = $schema[0]['indices'][0]; 99 | 100 | $this->assertEquals('PRIMARY', $indices->name); 101 | $this->assertEquals('PRIMARY', $indices->type); 102 | 103 | $this->assertContains('id', $indices->columns); 104 | 105 | DB::statement('DROP TABLE `tests`'); 106 | } 107 | 108 | public function testUniqueKey() 109 | { 110 | DB::statement( 111 | "CREATE TABLE `tests` ( 112 | `service` VARCHAR(255) NOT NULL, 113 | `uid` VARCHAR(255) NOT NULL, 114 | UNIQUE KEY `service_uid_unique` (`service`,`uid`) 115 | )" 116 | ); 117 | 118 | $schema = Parser::getSchema(); 119 | $indices = $schema[0]['indices'][0]; 120 | 121 | $this->assertEquals('service_uid_unique', $indices->name); 122 | $this->assertEquals('UNIQUE', $indices->type); 123 | 124 | $this->assertContains('service', $indices->columns); 125 | $this->assertContains('uid', $indices->columns); 126 | 127 | DB::statement('DROP TABLE `tests`'); 128 | } 129 | 130 | public function testIndexKey() 131 | { 132 | DB::statement( 133 | "CREATE TABLE `tests` ( 134 | `user_id` INT(10) UNSIGNED NOT NULL, 135 | KEY `user_id_index` (`user_id`) 136 | )" 137 | ); 138 | 139 | $schema = Parser::getSchema(); 140 | $indices = $schema[0]['indices'][0]; 141 | 142 | $this->assertEquals('user_id_index', $indices->name); 143 | $this->assertEquals('INDEX', $indices->type); 144 | 145 | $this->assertContains('user_id', $indices->columns); 146 | 147 | DB::statement('DROP TABLE `tests`'); 148 | } 149 | 150 | public function testExcludeSingleTable() 151 | { 152 | DB::statement( 153 | "CREATE TABLE `tests` ( 154 | `tests` VARCHAR(1) 155 | )" 156 | ); 157 | 158 | DB::statement( 159 | "CREATE TABLE `should_on_result` ( 160 | `tests` VARCHAR(1) 161 | )" 162 | ); 163 | 164 | $exclude = ['tests']; 165 | $expect = ['should_on_result']; 166 | 167 | $schema = Parser::getSchema($exclude); 168 | $tables = $schema->pluck('name')->toArray(); 169 | 170 | $this->assertEquals($expect, $tables); 171 | 172 | DB::statement('DROP TABLE `tests`'); 173 | 174 | DB::statement('DROP TABLE `should_on_result`'); 175 | } 176 | 177 | public function testExcludeMultipleTable() 178 | { 179 | DB::statement( 180 | "CREATE TABLE `tests1` ( 181 | `tests` VARCHAR(1) 182 | )" 183 | ); 184 | 185 | DB::statement( 186 | "CREATE TABLE `tests2` ( 187 | `tests` VARCHAR(1) 188 | )" 189 | ); 190 | 191 | $exclude = ['tests1', 'tests2']; 192 | $expect = []; 193 | 194 | $schema = Parser::getSchema($exclude); 195 | $tables = $schema->pluck('name')->toArray(); 196 | 197 | $this->assertEquals($expect, $tables); 198 | 199 | DB::statement('DROP TABLE `tests1`'); 200 | 201 | DB::statement('DROP TABLE `tests2`'); 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | setMocks(); 18 | } 19 | 20 | public function tearDown() 21 | { 22 | m::close(); 23 | } 24 | 25 | protected function setMocks() 26 | { 27 | $app = m::mock(Container::class); 28 | $app->shouldReceive('instance'); 29 | 30 | DB::setFacadeApplication($app); 31 | DB::swap(Manager::connection()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | load(); 20 | 21 | // setup database 22 | $database = new Illuminate\Database\Capsule\Manager; 23 | $database->addConnection([ 24 | 'driver' => 'mysql', 25 | 'host' => getenv('DB_HOST', '127.0.0.1'), 26 | 'port' => getenv('DB_PORT', '3306'), 27 | 'database' => getenv('DB_DATABASE', 'forge'), 28 | 'username' => getenv('DB_USERNAME', 'forge'), 29 | 'password' => getenv('DB_PASSWORD', ''), 30 | 'charset' => 'utf8', 31 | 'collation' => 'utf8_unicode_ci', 32 | 'prefix' => '', 33 | 'strict' => true, 34 | 'engine' => null, 35 | ]); 36 | $database->bootEloquent(); 37 | $database->setAsGlobal(); 38 | --------------------------------------------------------------------------------