├── .gitignore ├── .travis.yml ├── composer.json ├── phpunit.xml ├── readme.md ├── src ├── DbExporter │ ├── Commands │ │ ├── CopyToRemoteCommand.php │ │ ├── GeneratorCommand.php │ │ ├── MigrationsGeneratorCommand.php │ │ └── SeedGeneratorCommand.php │ ├── DbExportHandler.php │ ├── DbExportHandlerServiceProvider.php │ ├── DbExporter.php │ ├── DbMigrations.php │ ├── DbMigrationsServiceProvider.php │ ├── DbSeeding.php │ ├── Exceptions │ │ └── InvalidDatabaseException.php │ ├── Facades │ │ ├── DbExportHandler.php │ │ └── DbMigrations.php │ ├── SeederHelper.php │ └── stubs │ │ ├── migration.stub │ │ └── seed.stub └── config │ └── db-exporter.php └── tests ├── .gitkeep ├── TestCase.php ├── Unit └── MigrateTest.php └── migrations └── 2017_10_02_124618_create_test_tables.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | .phpintel/ 6 | ### PhpStorm ### 7 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 8 | 9 | ## Directory-based project format 10 | .idea/ 11 | # if you remove the above rule, at least ignore user-specific stuff: 12 | # .idea/workspace.xml 13 | # .idea/tasks.xml 14 | # and these sensitive or high-churn files: 15 | # .idea/dataSources.ids 16 | # .idea/dataSources.xml 17 | # .idea/sqlDataSources.xml 18 | # .idea/dynamic.xml 19 | 20 | ## File-based project format 21 | *.ipr 22 | *.iml 23 | *.iws 24 | 25 | ## Additional for IntelliJ 26 | out/ 27 | 28 | # generated by mpeltonen/sbt-idea plugin 29 | .idea_modules/ 30 | 31 | # generated by JIRA plugin 32 | atlassian-ide-plugin.xml 33 | 34 | # generated by Crashlytics plugin (for Android Studio and Intellij) 35 | com_crashlytics_export_strings.xml 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | 8 | before_script: 9 | - curl -s http://getcomposer.org/installer | php 10 | - php composer.phar install --dev 11 | 12 | script: phpunit -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elimuswift/db-exporter", 3 | "description": "Export your database quickly and easily as a Laravel Migration and all the data as a Seeder class.", 4 | "keywords": ["Export", "PHP", "Database", "Migrations", "Laravel", "Seed", "Command", "Artisan"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Master Weez", 9 | "email": "wizqydy@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.5.9", 14 | "illuminate/support": "^5.2", 15 | "illuminate/database": "^5.2", 16 | "illuminate/filesystem": "^5.2" 17 | 18 | }, 19 | "require-dev": { 20 | "orchestra/testbench": "~3.0", 21 | "orchestra/database": "^3.5" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Elimuswift\\DbExporter\\": "src/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4" : { 30 | "DbExporter\\Tests\\" : "tests" 31 | } 32 | }, 33 | "extra": { 34 | "laravel" : { 35 | "providers": [ 36 | "Elimuswift\\DbExporter\\DbExportHandlerServiceProvider" 37 | ] 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | tests 12 | 13 | 14 | 15 | 16 | src 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Elimuswift/db-exporter/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Elimuswift/db-exporter/?branch=master) 2 | [![Latest Stable Version](https://poser.pugx.org/elimuswift/db-exporter/v/stable.svg)](https://packagist.org/packages/elimuswift/db-exporter) [![Total Downloads](https://poser.pugx.org/elimuswift/db-exporter/d/total)](https://packagist.org/packages/elimuswift/db-exporter) [![Latest Unstable Version](https://poser.pugx.org/elimuswift/db-exporter/v/unstable.svg)](https://packagist.org/packages/elimuswift/db-exporter) [![License](https://poser.pugx.org/elimuswift/db-exporter/license.svg)](https://packagist.org/packages/elimuswift/db-exporter) 3 | 4 | # Database Exporter 5 | 6 | Export your database quickly and easily as a Laravel Migration and all the data as a Seeder class. This can be done via artisan commands or a controller action. 7 | 8 | 9 | Please note that I've only tested this package on a **MySQL** database. It has been confirmed it does not work with Postgres 10 | ## Installation 11 | 12 | Add `"elimuswift/db-exporter"`* as a requirement to `composer.json`: 13 | 14 | ```php 15 | { 16 | "require": { 17 | "elimuswift/db-exporter": "*" 18 | }, 19 | } 20 | ``` 21 | 22 | Update composer: 23 | 24 | ``` 25 | php composer.phar update 26 | ``` 27 | 28 | For laravel `<=5.4`, Add the service provider to `config/app.php`: 29 | 30 | ```php 31 | Elimuswift\DbExporter\DbExportHandlerServiceProvider::class 32 | ``` 33 | 34 | (Optional) Publish the configuration file. 35 | 36 | ``` 37 | php artisan vendor:publish --provider="Elimuswift\DbExporter\DbExportHandlerServiceProvider" 38 | ``` 39 | 40 | After publishing the config file make sure you change storage location for migrations and seeds. 41 | 42 | Use `dev-master` as version requirement to be on the cutting edge 43 | 44 | 45 | ## Documentation 46 | 47 | ### From the commandline 48 | 49 | #### Export database to migration 50 | 51 | **Basic usage** 52 | 53 | ``` 54 | php artisan db-exporter:migrations 55 | ``` 56 | 57 | **Specify a database** 58 | 59 | ``` 60 | php artisan db-exporter:migrations otherDatabaseName 61 | ``` 62 | 63 | **Ignoring tables** 64 | 65 | You can ignore multiple tables by seperating them with a comma. 66 | 67 | ``` 68 | php artisan db-exporter:migrations --ignore="table1,table2" 69 | ``` 70 | 71 | #### Export database table data to seed class 72 | This command will export all your database table data into a seed class. 73 | 74 | ``` 75 | php artisan db-exporter:seeds 76 | ``` 77 | **Important**: This requires your database config file to be updated in `config/database.php`** 78 | 79 | 80 | #### Uploading migrations/seeds to Storage Disk 81 | 82 | 83 | **Important:** The package backup destinations paths should match your desired disk location 84 | 85 | 86 | You can backup migrations and / or seeds to a storage disk that you application supports. 87 | 88 | 89 | ``` 90 | php artisan db-exporter:backup --migrations 91 | ``` 92 | Or **upload the seeds to the production server:** 93 | 94 | ``` 95 | php artisan db-exporter:backup --seeds 96 | ``` 97 | Or even combine the two: 98 | 99 | ``` 100 | php artisan db-exporter:backup --migrations --seeds 101 | ``` 102 | 103 | ***This feature uses Laravel's filesystem***. 104 | 105 | You must configure your storage and then specify the disk name in the config file. The default disk is local 106 | 107 | 108 | #### Export current database 109 | 110 | This class will export the database name from your `config/database.php` file, based on your 'default' option. 111 | 112 | 113 | 114 | ```php 115 | DbExporter::migrate(); 116 | ``` 117 | 118 | #### Export a custom database 119 | 120 | ```php 121 | DbExporter::migrate('otherDatabaseName'); 122 | ``` 123 | 124 | #### Database to seed 125 | 126 | 127 | This will write a seeder class with all the data of the current database. 128 | 129 | ```php 130 | DbExporter::seed(); 131 | ``` 132 | #### Seed a custom database 133 | Just pass the nameof the database to be seeded. 134 | 135 | ```php 136 | DbExporter::seed('myOtherDB'); 137 | ``` 138 | Next all you have to do is add the call method on the base seed class: 139 | 140 | ```php 141 | $this->call('nameOfYourSeedClass'); 142 | ``` 143 | 144 | Now you can run from the commmand line: 145 | 146 | `php artisan db:seed`, or, without having to add the call method: `php artisan db:seed --class=nameOfYourSeedClass` 147 | 148 | #### Chaining 149 | You can also combine the generation of the migrations & the seed: 150 | 151 | ```php 152 | DbExporter::migrate()->seed(); 153 | ``` 154 | Or with: 155 | 156 | ```php 157 | DbExporter::migrateAndSeed(); 158 | ``` 159 | 160 | #### Ignoring tables 161 | By default the migrations table is ignored. You can add tabled to ignore with the following syntax: 162 | 163 | ```php 164 | DbExporter::ignore('tableToIgnore')->migrateAndSeed(); 165 | DbExporter::ignore('table1','table2','table3')->migrateAndSeed(); 166 | ``` 167 | You can also pass an array of tables to ignore. 168 | 169 | ### From the configuration file 170 | 171 | #### Ignore tables for seeder 172 | 173 | If you want to always ignore certain tables you can do it on the config file 174 | 175 | ``` 176 | return [ 177 | 'seeds' => [ 178 | 'ignore_tables' => [ 179 | 'table_to_ignore1', 180 | 'table_to_ignore2' 181 | ] 182 | ] 183 | ]; 184 | ``` 185 | 186 | With this configuration every time when the command `php artisan db-exporter:seeds` is executed will ignore the tables on the array 187 | 188 | #### Just use selected tables for seeder 189 | 190 | In the other hand, If you want to use always certain tables you can do it on the config file 191 | 192 | ``` 193 | return [ 194 | 'seeds' => [ 195 | 'use_tables' => [ 196 | 'table_to_ignore1', 197 | 'table_to_ignore2' 198 | ] 199 | ] 200 | ]; 201 | ``` 202 | 203 | With this configuration every time when the command `php artisan db-exporter:seeds` is executed will only be executed to the tables on the array 204 | 205 | ## Credits 206 | Credits to **@nWidart** the original creator of the package [DbExporter](https://github.com/nWidart/DbExporter). I couldn't get it working as-is, so I decided to rewrite the package to fit the latest versions of laravel, and added a couple a features of my own. 207 | 208 | 209 | -------------------------------------------------------------------------------- /src/DbExporter/Commands/CopyToRemoteCommand.php: -------------------------------------------------------------------------------- 1 | handleOptions(); 33 | foreach ($this->uploadedFiles as $type => $files) { 34 | $this->line("\n"); 35 | $this->info(ucfirst($type)); 36 | foreach ($files as $file) { 37 | $this->sectionMessage($type, $file.' uploaded.'); 38 | } 39 | } 40 | 41 | $disk = $this->getDiskName(); 42 | $this->blockMessage('Success!', "Everything uploaded to $disk filesystem!"); 43 | } 44 | 45 | //end fire() 46 | 47 | protected function getOptions() 48 | { 49 | return [ 50 | [ 51 | 'migrations', 52 | 'm', 53 | InputOption::VALUE_NONE, 54 | 'Upload the migrations to a storage.', 55 | null, 56 | ], 57 | [ 58 | 'seeds', 59 | 's', 60 | InputOption::VALUE_NONE, 61 | 'Upload the seeds to the remote host.', 62 | null, 63 | ], 64 | ]; 65 | } 66 | 67 | //end getOptions() 68 | 69 | protected function handleOptions() 70 | { 71 | $options = $this->option(); 72 | foreach ($options as $key => $value) { 73 | if ($value) { 74 | $this->upload($key); 75 | } 76 | } 77 | } 78 | 79 | protected function upload($what) 80 | { 81 | $localPath = Config::get('db-exporter.export_path.'.$what); 82 | $dir = scandir($localPath); 83 | $remotePath = Config::get('db-exporter.remote.'.$what); 84 | $this->line("\n"); 85 | $this->info(ucfirst($what)); 86 | // Reset file coounter 87 | static::$filesCount = 0; 88 | // Prepare the progress bar 89 | array_walk($dir, function ($file) { 90 | if ($this->ignoredFile($file)) { 91 | return; 92 | } 93 | ++static::$filesCount; 94 | }); 95 | $progress = $this->output->createProgressBar(static::$filesCount); 96 | foreach ($dir as $file) { 97 | if ($this->ignoredFile($file)) { 98 | $this->comment("---> ignoring $file "); 99 | continue; 100 | } 101 | 102 | // Capture the uploaded files for displaying later 103 | $this->uploadedFiles[$what][] = $remotePath.$file; 104 | 105 | // Copy the files 106 | Storage::disk($this->getDiskName())->put( 107 | $remotePath.$file, 108 | $localPath.'/'.$file 109 | ); 110 | $progress->advance(); 111 | } 112 | 113 | $progress->finish(); 114 | 115 | return true; 116 | } 117 | 118 | //end upload() 119 | 120 | /** 121 | * @return string|null 122 | */ 123 | protected function getDiskName() 124 | { 125 | // For now static from he config file. 126 | return Config::get('db-exporter.backup.disk'); 127 | } 128 | 129 | /** 130 | * Determine if a file should be ignored. 131 | * 132 | * @param string $file filename 133 | * 134 | * @return bool 135 | **/ 136 | protected function ignoredFile($file) 137 | { 138 | if (in_array($file, $this->ignoredFiles)) { 139 | return true; 140 | } 141 | 142 | return false; 143 | } 144 | 145 | //end getDiskName() 146 | }//end class 147 | -------------------------------------------------------------------------------- /src/DbExporter/Commands/GeneratorCommand.php: -------------------------------------------------------------------------------- 1 | getHelperSet()->get('formatter'); 39 | $errorMessages = [ 40 | $title, 41 | $message, 42 | ]; 43 | $formattedBlock = $formatter->formatBlock($errorMessages, $style, true); 44 | $this->line($formattedBlock); 45 | } 46 | 47 | //end blockMessage() 48 | 49 | protected function sectionMessage($title, $message) 50 | { 51 | $formatter = $this->getHelperSet()->get('formatter'); 52 | $formattedLine = $formatter->formatSection( 53 | $title, 54 | $message 55 | ); 56 | $this->line($formattedLine); 57 | } 58 | 59 | //end sectionMessage() 60 | 61 | protected function getArguments() 62 | { 63 | return [ 64 | [ 65 | 'database', 66 | InputArgument::OPTIONAL, 67 | 'Override the application database', 68 | ], 69 | ]; 70 | } 71 | 72 | //end getArguments() 73 | 74 | protected function getOptions() 75 | { 76 | return [ 77 | [ 78 | 'ignore', 79 | 'i', 80 | InputOption::VALUE_REQUIRED, 81 | 'Ignore tables to export, seperated by a comma', 82 | null, 83 | ], 84 | ]; 85 | } 86 | 87 | //end getOptions() 88 | 89 | protected function fireAction($action, $database) 90 | { 91 | // Grab the options 92 | $ignore = $this->option('ignore'); 93 | $this->database = $database; 94 | if (empty($ignore)) { 95 | $this->handler->$action($database); 96 | } else { 97 | $tables = explode(',', str_replace(' ', '', $ignore)); 98 | DbExporter::$ignore = array_merge(DbExporter::$ignore, $tables); 99 | $this->handler->$action($database); 100 | foreach ($tables as $table) { 101 | $this->comment("Ignoring the {$table} table"); 102 | } 103 | } 104 | } 105 | 106 | //end fireAction() 107 | }//end class 108 | -------------------------------------------------------------------------------- /src/DbExporter/Commands/MigrationsGeneratorCommand.php: -------------------------------------------------------------------------------- 1 | handler = $handler; 22 | } 23 | 24 | //end __construct() 25 | 26 | public function handle() 27 | { 28 | $database = $this->argument('database'); 29 | 30 | // Display some helpfull info 31 | if (empty($database)) { 32 | $this->comment("Preparing the migrations for database: {$this->getDatabaseName()}"); 33 | $database = $this->getDatabaseName(); 34 | } else { 35 | $this->comment("Preparing the migrations for database {$database}"); 36 | } 37 | 38 | $this->fireAction('migrate', $database); 39 | 40 | // Symfony style block messages 41 | $this->blockMessage('Success!', 'Database migrations generated in: '.$this->handler->getMigrationsFilePath()); 42 | } 43 | 44 | //end fire() 45 | }//end class 46 | -------------------------------------------------------------------------------- /src/DbExporter/Commands/SeedGeneratorCommand.php: -------------------------------------------------------------------------------- 1 | handler = $handler; 25 | } 26 | 27 | //end __construct() 28 | 29 | public function handle() 30 | { 31 | $database = $this->argument('database'); 32 | // Display some helpfull info 33 | if (empty($database)) { 34 | $this->comment("Preparing the seeder class for database {$this->getDatabaseName()}"); 35 | } else { 36 | $this->comment("Preparing the seeder class for database {$database}"); 37 | } 38 | 39 | // Grab the options 40 | $this->fireAction('seed', $database); 41 | // Symfony style block messages 42 | $formatter = $this->getHelperSet()->get('formatter'); 43 | $filename = $this->getFilename(); 44 | $errorMessages = [ 45 | 'Success!', 46 | "Database seed class generated in: {$filename}", 47 | ]; 48 | $formattedBlock = $formatter->formatBlock($errorMessages, 'info', true); 49 | $this->line($formattedBlock); 50 | } 51 | 52 | //end fire() 53 | 54 | private function getFilename() 55 | { 56 | $filename = ucfirst(Str::camel($this->database)).'DatabaseSeeder'; 57 | 58 | return Config::get('db-exporter.export_path.seeds')."/{$filename}.php"; 59 | } 60 | 61 | //end getFilename() 62 | }//end class 63 | -------------------------------------------------------------------------------- /src/DbExporter/DbExportHandler.php: -------------------------------------------------------------------------------- 1 | migrator = $DbMigrations; 26 | $this->seeder = $DbSeeding; 27 | } 28 | 29 | //end __construct() 30 | 31 | /** 32 | * Create migrations from the given DB. 33 | * 34 | * @param string null $database 35 | * 36 | * @return $this 37 | */ 38 | public function migrate($database = null) 39 | { 40 | $this->migrator->convert($database)->write(); 41 | 42 | return $this; 43 | } 44 | 45 | //end migrate() 46 | 47 | /** 48 | * @param null $database 49 | * 50 | * @return $this 51 | */ 52 | public function seed($database = null) 53 | { 54 | $this->seeder->convert($database)->write(); 55 | 56 | return $this; 57 | } 58 | 59 | //end seed() 60 | 61 | /** 62 | * Helper function to generate the migration and the seed in one command. 63 | * 64 | * @param null $database 65 | * 66 | * @return $this 67 | */ 68 | public function migrateAndSeed($database = null) 69 | { 70 | // Run the migrator generator 71 | $this->migrator->convert($database)->write(); 72 | 73 | // Run the seeder generator 74 | $this->seeder->convert($database)->write(); 75 | 76 | return $this; 77 | } 78 | 79 | //end migrateAndSeed() 80 | 81 | /** 82 | * Add tables to the ignore array. 83 | * 84 | * @param $tables 85 | * 86 | * @return $this 87 | */ 88 | public function ignore(...$tables) 89 | { 90 | DbExporter::$ignore = array_merge(DbExporter::$ignore, (array)$tables); 91 | 92 | return $this; 93 | } 94 | 95 | //end ignore() 96 | 97 | /** 98 | * @return mixed 99 | */ 100 | public function getMigrationsFilePath() 101 | { 102 | return DbMigrations::$filePath; 103 | } 104 | 105 | //end getMigrationsFilePath() 106 | 107 | public function uploadTo($remote) 108 | { 109 | DbExporter::$remote = $remote; 110 | 111 | return $this; 112 | } 113 | 114 | //end uploadTo() 115 | }//end class 116 | -------------------------------------------------------------------------------- /src/DbExporter/DbExportHandlerServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes( 35 | [ 36 | realpath(__DIR__ . '/../') . '/config/db-exporter.php' => config_path('db-exporter.php'), 37 | ], 38 | 'config' 39 | ); 40 | 41 | $this->mergeConfigFrom( 42 | realpath(__DIR__ . '/../') . '/config/db-exporter.php', 43 | 'db-exporter' 44 | ); 45 | // Instatiate a new DbMigrations class to send to the handler 46 | $this->migrator = $migrator; 47 | // Load the alias 48 | $this->loadAlias(); 49 | } 50 | 51 | //end boot() 52 | 53 | public function register() 54 | { 55 | $this->app->register(DbMigrationsServiceProvider::class); 56 | // Register the base export handler class 57 | $this->registerDbExportHandler(); 58 | // Handle the artisan commands 59 | $this->registerCommands(); 60 | } 61 | 62 | //end register() 63 | 64 | public function provides() 65 | { 66 | return ['DbExporter']; 67 | } 68 | 69 | //end provides() 70 | 71 | /** 72 | * Register the needed commands. 73 | */ 74 | public function registerCommands() 75 | { 76 | $commands = [ 77 | 'Migrations', 78 | 'Seeds', 79 | 'Backup', 80 | ]; 81 | 82 | foreach ($commands as $command) { 83 | $this->{"register{$command}Command"}(); 84 | } 85 | 86 | // Once the commands are registered in the application IoC container we will 87 | // register them with the Artisan start event so that these are available 88 | // when the Artisan application actually starts up and is getting used. 89 | $this->commands('db-exporter.migrations', 'db-exporter.seeds', 'db-exporter.backup'); 90 | } 91 | 92 | //end registerCommands() 93 | 94 | /** 95 | * Register the migrations command. 96 | */ 97 | protected function registerMigrationsCommand() 98 | { 99 | $this->app->singleton( 100 | 'db-exporter.migrations', 101 | function($app) { 102 | return new Commands\MigrationsGeneratorCommand($app[DbExportHandler::class]); 103 | } 104 | ); 105 | } 106 | 107 | //end registerMigrationsCommand() 108 | 109 | /** 110 | * Register the seeds command. 111 | */ 112 | protected function registerSeedsCommand() 113 | { 114 | $this->app->singleton( 115 | 'db-exporter.seeds', 116 | function($app) { 117 | return new Commands\SeedGeneratorCommand($app[DbExportHandler::class]); 118 | } 119 | ); 120 | } 121 | 122 | //end registerSeedsCommand() 123 | 124 | protected function registerBackupCommand() 125 | { 126 | $this->app->singleton( 127 | 'db-exporter.backup', 128 | function() { 129 | return new Commands\CopyToRemoteCommand(); 130 | } 131 | ); 132 | } 133 | 134 | //end registerBackupCommand() 135 | 136 | /** 137 | * Register the Export handler class. 138 | */ 139 | protected function registerDbExportHandler() 140 | { 141 | $this->app->bind( 142 | DbExportHandler::class, 143 | function($app) { 144 | // Instatiate a new DbSeeding class to send to the handler 145 | $seeder = new DbSeeding($app[DbMigrations::class]->database); 146 | 147 | // Instantiate the handler 148 | return new DbExportHandler($app[DbMigrations::class], $seeder); 149 | } 150 | ); 151 | 152 | $this->app->bind( 153 | 'DbExporter', 154 | function($app) { 155 | return $app[DbExportHandler::class]; 156 | } 157 | ); 158 | } 159 | 160 | //end registerDbExportHandler() 161 | 162 | /** 163 | * Load the alias = One less install step for the user. 164 | */ 165 | protected function loadAlias() 166 | { 167 | $loader = AliasLoader::getInstance(); 168 | $loader->alias('DbExporter', Facades\DbExportHandler::class); 169 | } 170 | 171 | //end loadAlias() 172 | }//end class 173 | -------------------------------------------------------------------------------- /src/DbExporter/DbExporter.php: -------------------------------------------------------------------------------- 1 | getPdo(); 58 | 59 | return $pdo->query('SELECT table_name FROM information_schema.tables WHERE table_schema="'.$this->database.'"'); 60 | } 61 | 62 | public function getTableIndexes($table) 63 | { 64 | $pdo = DB::connection()->getPdo(); 65 | 66 | return $pdo->query('SHOW INDEX FROM '.$this->database.'.'.$table.' WHERE Key_name != "PRIMARY"'); 67 | } 68 | 69 | /** 70 | * Get all the columns for a given table. 71 | * 72 | * @param $table 73 | * 74 | * @return array 75 | */ 76 | protected function getTableDescribes($table) 77 | { 78 | return DB::table('information_schema.columns') 79 | ->where('table_schema', '=', $this->database) 80 | ->where('table_name', '=', $table) 81 | ->get($this->selects); 82 | } 83 | 84 | /** 85 | * Get all the foreign key constraints for a given table. 86 | * 87 | * @param $table 88 | * 89 | * @return array 90 | */ 91 | protected function getTableConstraints($table) 92 | { 93 | return DB::table('information_schema.key_column_usage') 94 | ->distinct() 95 | ->join('information_schema.REFERENTIAL_CONSTRAINTS', 'REFERENTIAL_CONSTRAINTS.CONSTRAINT_NAME', '=', 'key_column_usage.CONSTRAINT_NAME') 96 | ->where('key_column_usage.table_schema', '=', $this->database) 97 | ->where('key_column_usage.table_name', '=', $table) 98 | ->get($this->constraints); 99 | } 100 | 101 | /** 102 | * Grab all the table data. 103 | * 104 | * @param $table 105 | * 106 | * @return mixed 107 | */ 108 | protected function getTableData($table) 109 | { 110 | return DB::table($this->database.'.'.$table)->get(); 111 | } 112 | 113 | /** 114 | * Try to create directories if they dont exist. 115 | * 116 | * @param string $path 117 | **/ 118 | protected function makePath($path) 119 | { 120 | $del = DIRECTORY_SEPARATOR; 121 | $dir = ''; 122 | $directories = explode($del, $path); 123 | foreach ($directories as $directory) { 124 | if (!empty($directory)) { 125 | $dir .= $del.$directory; 126 | } 127 | 128 | if (!is_dir($dir)) { 129 | @mkdir($dir, 0775, true); 130 | } 131 | } 132 | } 133 | 134 | /** 135 | * Write the file. 136 | * 137 | * @return mixed 138 | */ 139 | abstract public function write(); 140 | 141 | /** 142 | * Convert the database to a usefull format. 143 | * 144 | * @param null $database 145 | * 146 | * @return mixed 147 | */ 148 | abstract public function convert($database = null); 149 | 150 | /** 151 | * Put the converted stub into a template. 152 | * 153 | * @return mixed 154 | */ 155 | abstract protected function compile($table); 156 | } 157 | -------------------------------------------------------------------------------- /src/DbExporter/DbMigrations.php: -------------------------------------------------------------------------------- 1 | 'integer', 19 | 'smallint' => 'smallInteger', 20 | 'bigint' => 'bigInteger', 21 | 'char ' => 'string', 22 | 'varchar' => 'string', 23 | 'float' => 'float', 24 | 'double' => 'double', 25 | 'decimal' => 'decimal', 26 | 'tinyint' => 'tinyInteger', 27 | 'date' => 'date', 28 | 'timestamp' => 'timestamp', 29 | 'datetime' => 'dateTime', 30 | 'longtext' => 'longText', 31 | 'mediumtext' => 'mediumText', 32 | 'text' => 'text', 33 | 'longblob' => 'binary', 34 | 'blob' => 'binary', 35 | 'enum' => 'enum', 36 | 'char' => 'char ', 37 | 'geometry' => 'geometry', 38 | 'time' => 'time', 39 | 'point' => 'point', 40 | 'polygon' => 'polygon', 41 | 'multipolygon' => 'muliPolygon', 42 | 'multilinestring' => 'multiLineString', 43 | 'mulitpoint' => 'multiPoint', 44 | 'mediumint' => 'mediumInteger', 45 | 'mac' => 'macAddress', 46 | 'json' => 'json', 47 | 'linestring' => 'lineString', 48 | 'geometrycollection' => 'geometryCollection', 49 | 'bool' => 'boolean', 50 | 'year' => 'year', 51 | ]; 52 | /** 53 | * Primary key column types. 54 | * 55 | * @var array 56 | **/ 57 | protected $primaryKeys = [ 58 | 'bigint' => 'bigIncrements', 59 | 'int' => 'increments', 60 | ]; 61 | 62 | protected $schema; 63 | 64 | protected $customDb = false; 65 | 66 | public static $filePath; 67 | 68 | protected $primaryKey; 69 | 70 | protected $defaultLength; 71 | 72 | protected $methodName; 73 | /** 74 | * File name for migration file. 75 | * 76 | * @var string 77 | */ 78 | public $filename; 79 | 80 | /** 81 | * Set the database name. 82 | * 83 | * @param string $database 84 | * @throw InvalidDatabaseException 85 | */ 86 | public function __construct($database) 87 | { 88 | if (empty($database)) { 89 | throw new InvalidDatabaseException('No database set in app/config/database.php'); 90 | } 91 | 92 | $this->database = $database; 93 | } 94 | 95 | //end __construct() 96 | 97 | /** 98 | * Write the prepared migration to a file. 99 | */ 100 | public function write() 101 | { 102 | // Check if convert method was called before 103 | // If not, call it on default DB 104 | if (!$this->customDb) { 105 | $this->convert(); 106 | } 107 | 108 | $schema = $this->compile(); 109 | $absolutePath = Config::get('db-exporter.export_path.migrations'); 110 | $this->makePath($absolutePath); 111 | $this->filename = date('Y_m_d_His').'_create_'.$this->database.'_database.php'; 112 | static::$filePath = $absolutePath."/{$this->filename}"; 113 | file_put_contents(static::$filePath, $schema); 114 | 115 | return static::$filePath; 116 | } 117 | 118 | //end write() 119 | 120 | /** 121 | * Convert the database to migrations 122 | * If none is given, use de DB from condig/database.php. 123 | * 124 | * @param null $database 125 | * 126 | * @return $this 127 | */ 128 | public function convert($database = null) 129 | { 130 | if (!is_null($database)) { 131 | $this->database = $database; 132 | $this->customDb = true; 133 | } 134 | 135 | $tables = $this->getTables(); 136 | 137 | // Loop over the tables 138 | foreach ($tables as $key => $value) { 139 | // Do not export the ignored tables 140 | if (in_array($value['table_name'], static::$ignore)) { 141 | continue; 142 | } 143 | 144 | $down = "Schema::dropIfExists('{$value['table_name']}');"; 145 | $up = "Schema::create('{$value['table_name']}', function(Blueprint $"."table) {\n"; 146 | 147 | $tableDescribes = $this->getTableDescribes($value['table_name']); 148 | // Loop over the tables fields 149 | foreach ($tableDescribes as $values) { 150 | $para = strpos($values->Type, '('); 151 | $type = $para > -1 ? substr($values->Type, 0, $para) : $values->Type; 152 | $nullable = 'NO' == $values->Nullable ? '' : '->nullable()'; 153 | $default = empty($values->Default) || 'NULL' == $values->Default ? '' : "->default({$values->Default})"; 154 | $default = 'CURRENT_TIMESTAMP' == $values->Default || 'current_timestamp()' == $values->Default ? '->useCurrent()' : $default; 155 | $unsigned = false === strpos($values->Type, 'unsigned') ? '' : '->unsigned()'; 156 | $this->hasDefaults($type, $values); 157 | $this->methodName = $this->columnType($type); 158 | if ('PRI' == $values->Key) { 159 | $this->primaryKey = '->primary()'; 160 | if ($methodName = $this->columnType($values->Data_Type, 'primaryKeys') && 'auto_increment' == $values->Extra) { 161 | $this->primaryKey = '->autoIncrement()'; 162 | } 163 | } 164 | 165 | $up .= ' $'."table->{$this->methodName}('{$values->Field}'{$this->defaultLength}){$this->primaryKey}{$nullable}{$default}{$unsigned};\n"; 166 | $this->unsetData(); 167 | }//end foreach 168 | 169 | $tableIndexes = (array) $this->getTableIndexes($value['table_name']); 170 | if (!is_null($tableIndexes) && count($tableIndexes)) { 171 | foreach ($tableIndexes as $index) { 172 | if (Str::endsWith(@$index['Key_name'], '_index')) { 173 | $up .= ' $'."table->index('".$index['Column_name']."');\n"; 174 | } 175 | } 176 | } 177 | 178 | $up .= " });\n\n"; 179 | $Constraint = $ConstraintDown = ''; 180 | /* 181 | * @var array 182 | */ 183 | $tableConstraints = $this->getTableConstraints($value['table_name']); 184 | if (!is_null($tableConstraints) && $tableConstraints->count()) { 185 | $Constraint = $ConstraintDown = " 186 | Schema::table('{$value['table_name']}', function(Blueprint $"."table) {\n"; 187 | $tables = []; 188 | foreach ($tableConstraints as $foreign) { 189 | if (!in_array($foreign->Field, $tables)) { 190 | $field = "{$foreign->Table}_{$foreign->Field}_foreign"; 191 | $ConstraintDown .= ' $'."table->dropForeign('".$field."');\n"; 192 | $Constraint .= ' $'."table->foreign('".$foreign->Field."')->references('".$foreign->References."')->on('".$foreign->ON."')->onDelete('".$foreign->onDelete."');\n"; 193 | $tables[$foreign->Field] = $foreign->Field; 194 | } 195 | } 196 | 197 | $Constraint .= " });\n\n"; 198 | $ConstraintDown .= " });\n\n"; 199 | } 200 | 201 | $this->schema[$value['table_name']] = [ 202 | 'up' => $up, 203 | 'constraint' => $Constraint, 204 | 'constraint_down' => $ConstraintDown, 205 | 'down' => $down, 206 | ]; 207 | }//end foreach 208 | 209 | return $this; 210 | } 211 | 212 | //end convert() 213 | 214 | public function columnType($type, $columns = 'columns', $method = '') 215 | { 216 | return array_key_exists($type, $this->{$columns}) ? $this->{$columns}[$type] : $method; 217 | } 218 | 219 | //end columnType() 220 | 221 | /** 222 | * Compile the migration into the base migration file 223 | * TODO use a template with seacrh&replace. 224 | * 225 | * @return string 226 | */ 227 | protected function compile($null = null) 228 | { 229 | $upSchema = ''; 230 | $downSchema = ''; 231 | $upConstraint = ''; 232 | $downConstraint = ''; 233 | 234 | // prevent of failure when no table 235 | if (!is_null($this->schema) && is_array($this->schema)) { 236 | foreach ($this->schema as $name => $values) { 237 | // check again for ignored tables 238 | if (in_array($name, self::$ignore)) { 239 | continue; 240 | } 241 | 242 | $upSchema .= " 243 | /** 244 | * Migration schema for table {$name} 245 | * 246 | */ 247 | {$values['up']}"; 248 | $upConstraint .= " 249 | {$values['constraint']}"; 250 | $downConstraint .= " 251 | {$values['constraint_down']}"; 252 | 253 | $downSchema .= " 254 | {$values['down']}"; 255 | } 256 | }//end if 257 | 258 | // Grab the template 259 | $template = File::get(__DIR__.'/stubs/migration.stub'); 260 | 261 | // Replace the classname 262 | $template = str_replace('{{name}}', 'Create'.ucfirst(Str::camel($this->database)).'Database', $template); 263 | 264 | // Replace the up and down values 265 | $template = str_replace('{{up}}', $upSchema, $template); 266 | $template = str_replace('{{down}}', $downSchema, $template); 267 | $template = str_replace('{{upCon}}', $upConstraint, $template); 268 | $template = str_replace('{{downCon}}', $downConstraint, $template); 269 | 270 | return $template; 271 | } 272 | 273 | /** 274 | * summary. 275 | * 276 | * @author 277 | */ 278 | public function hasDefaults($type, $column) 279 | { 280 | if ($hasSize = strpos($column->Type, '(')) { 281 | $values = substr($column->Type, ($hasSize + 1), -1); 282 | switch ($type) { 283 | case 'enum': 284 | $this->defaultLength = ', array('.$values.')'; 285 | break; 286 | case 'char': 287 | case 'varchar': 288 | case 'text': 289 | case 'mediumtext': 290 | case 'longtext': 291 | $this->defaultLength = ', '.$column->Length; 292 | break; 293 | case 'double': 294 | case 'float': 295 | case 'decimal': 296 | $this->defaultLength = ", $column->Precision, $column->Scale"; 297 | break; 298 | } 299 | } 300 | } 301 | 302 | /** 303 | * summary. 304 | * 305 | * @author 306 | */ 307 | protected function unsetData() 308 | { 309 | $this->primaryKey = null; 310 | $this->methodName = null; 311 | $this->defaultLength = null; 312 | } 313 | 314 | //end compile() 315 | }//end class 316 | -------------------------------------------------------------------------------- /src/DbExporter/DbMigrationsServiceProvider.php: -------------------------------------------------------------------------------- 1 | alias('DbMigrations', 'Facades\DbMigrations'); 25 | } 26 | 27 | //end boot() 28 | 29 | /** 30 | * Register the service provider. 31 | */ 32 | public function register() 33 | { 34 | $this->app->singleton( 35 | DbMigrations::class, 36 | function() { 37 | $connType = Config::get('database.default'); 38 | $database = Config::get('database.connections.' . $connType); 39 | 40 | return new DbMigrations($database['database']); 41 | } 42 | ); 43 | } 44 | 45 | //end register() 46 | 47 | /** 48 | * Get the services provided by the provider. 49 | * 50 | * @return array 51 | */ 52 | public function provides() 53 | { 54 | return ['DbMigrations']; 55 | } 56 | 57 | //end provides() 58 | }//end class 59 | -------------------------------------------------------------------------------- /src/DbExporter/DbSeeding.php: -------------------------------------------------------------------------------- 1 | database = $database; 40 | } 41 | 42 | //end __construct() 43 | 44 | /** 45 | * Write the seed file. 46 | */ 47 | public function write() 48 | { 49 | // Check if convert method was called before 50 | // If not, call it on default DB 51 | if (!$this->customDb) { 52 | $this->convert(); 53 | } 54 | 55 | foreach ($this->seedingStub as $table => $value) 56 | { 57 | $seed = $this->compile($table); 58 | $absolutePath = Config::get('db-exporter.export_path.seeds'); 59 | $this->filename = ucfirst(Str::camel($table)) . 'DatabaseSeeder'; 60 | $this->makePath($absolutePath); 61 | file_put_contents($absolutePath . "/{$this->filename}.php", $seed); 62 | } 63 | } 64 | 65 | //end write() 66 | 67 | /** 68 | * Convert the database tables to something usefull. 69 | * 70 | * @param null $database 71 | * 72 | * @return $this 73 | */ 74 | public function convert($database = null) 75 | { 76 | if (!is_null($database)) { 77 | $this->database = $database; 78 | $this->customDb = true; 79 | } 80 | 81 | // Get the tables for the database 82 | $tables = $this->getTables(); 83 | $result = []; 84 | 85 | // Get tables to ignore 86 | $config = config('db-exporter.seeds'); 87 | $ignore_tables = collect([]); 88 | if(!is_null($config) && isset($config['ignore_tables']) && !is_null($config['ignore_tables'])) { 89 | $ignore_tables = collect($config['ignore_tables']); 90 | } 91 | 92 | $show_tables = collect([]); 93 | if(!is_null($config) && isset($config['use_tables']) && !is_null($config['use_tables'])) { 94 | $show_tables = collect($config['use_tables']); 95 | } 96 | 97 | // Loop over the tables 98 | foreach ($tables as $key => $value) 99 | { 100 | if($show_tables->count() > 0 && !$show_tables->contains($value['table_name'])) { 101 | continue; 102 | } 103 | 104 | if($ignore_tables->contains($value['table_name'])) { 105 | continue; 106 | } 107 | 108 | // Do not export the ignored tables 109 | if (in_array($value['table_name'], self::$ignore)) { 110 | continue; 111 | } 112 | 113 | $tableName = $value['table_name']; 114 | $tableData = $this->getTableData($value['table_name']); 115 | $insertStub = ''; 116 | 117 | foreach ($tableData as $obj) { 118 | $insertStub .= " 119 | [\n"; 120 | foreach ($obj as $prop => $value) { 121 | $insertStub .= $this->insertPropertyAndValue($prop, $value); 122 | } 123 | 124 | if (count($tableData) > 1) { 125 | $insertStub .= " ],\n"; 126 | } else { 127 | $insertStub .= " ]\n"; 128 | } 129 | } 130 | 131 | $insertStub = " 132 | \$data = [ 133 | {$insertStub} 134 | ];"; 135 | 136 | if ($this->hasTableData($tableData)) { 137 | $stub = $insertStub.' 138 | 139 | foreach($data as $item) 140 | { 141 | $this->saveData("'.$tableName.'", $item); 142 | }'; 143 | $result[$tableName] = $stub; 144 | } 145 | }//end foreach 146 | 147 | $this->seedingStub = $result; 148 | 149 | return $this; 150 | } 151 | 152 | //end convert() 153 | 154 | /** 155 | * Compile the current seedingStub with the seed template. 156 | * 157 | * @return mixed 158 | */ 159 | protected function compile($table) 160 | { 161 | // Grab the template 162 | $template = File::get(__DIR__ . '/stubs/seed.stub'); 163 | 164 | // Replace the classname 165 | $template = str_replace('{{className}}', ucfirst(Str::camel($table)) . 'DatabaseSeeder', $template); 166 | $template = str_replace('{{run}}', $this->seedingStub[$table], $template); 167 | 168 | return $template; 169 | } 170 | 171 | //end compile() 172 | 173 | private function insertPropertyAndValue($prop, $value) 174 | { 175 | if(strlen($prop) > 0) { 176 | $prop = "'{$prop}'"; 177 | } else { 178 | $prop = 'null'; 179 | } 180 | 181 | if(strlen($value) > 0) { 182 | $value = str_replace("'", "\'", $value); 183 | $value = "'{$value}'"; 184 | } else { 185 | $value = 'null'; 186 | } 187 | return " {$prop} => {$value},\n"; 188 | } 189 | 190 | //end insertPropertyAndValue() 191 | 192 | /** 193 | * @param $tableData 194 | * 195 | * @return bool 196 | */ 197 | public function hasTableData($tableData) 198 | { 199 | return count($tableData) >= 1; 200 | } 201 | 202 | //end hasTableData() 203 | }//end class 204 | -------------------------------------------------------------------------------- /src/DbExporter/Exceptions/InvalidDatabaseException.php: -------------------------------------------------------------------------------- 1 | where('id', $item['id'])->count() > 0) 14 | { 15 | \DB::table($table)->where('id', $item['id'])->update($item); 16 | } 17 | else 18 | { 19 | \DB::table($table)->insert($item); 20 | } 21 | } 22 | else 23 | { 24 | $ids = collect($item)->filter(function($item, $key) { 25 | return str_contains($key, '_id'); 26 | })->keys()->values(); 27 | 28 | // If there isnt any column with _id, so check that every column matches 29 | if($ids->count() <= 0) { 30 | $ids = collect($item)->keys()->values(); 31 | } 32 | $object = \DB::table($table); 33 | foreach ($ids as $id) { 34 | $object = $object->where($id, $item[$id]); 35 | } 36 | 37 | // save or update 38 | if($object->count() > 0) 39 | { 40 | $object->update($item); 41 | } 42 | else 43 | { 44 | $object->insert($item); 45 | } 46 | 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/DbExporter/stubs/migration.stub: -------------------------------------------------------------------------------- 1 | [ 5 | /* 6 | * The disk where your files will be backed up 7 | **/ 8 | 'disk' => 'local', 9 | 10 | /* 11 | * Location on disk where to backup migratons 12 | **/ 13 | 'migrations' => 'backup/migrations/', 14 | 15 | /* 16 | * Location on disk where to backup seeds 17 | **/ 18 | 'seeds' => 'backup/seeds/', 19 | ], 20 | 'export_path' => [ 21 | 'migrations' => database_path('backup/migrations'), 22 | 'seeds' => database_path('backup/seeds'), 23 | ], 24 | 'seeds' => [ 25 | 'ignore_tables' => [ 26 | // Add tables 27 | ], 28 | 'use_tables' => [ 29 | // add tables 30 | ] 31 | ] 32 | ]; 33 | -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Elimuswift/db-exporter/da5fbd9d462bb4a26068475bbbdca5fbf304c0d7/tests/.gitkeep -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | 13 | **/ 14 | protected function getPackageProviders($app) 15 | { 16 | return ['Elimuswift\DbExporter\DbExportHandlerServiceProvider']; 17 | } 18 | 19 | /** 20 | * Define environment setup. 21 | * 22 | * @param \Illuminate\Foundation\Application $app 23 | */ 24 | protected function getEnvironmentSetUp($app) 25 | { 26 | // Setup default database to use sqlite :memory: 27 | 28 | $app['config']->set('database.default', 'testbench'); 29 | $app['config']->set('database.connections.testbench', [ 30 | 'driver' => 'sqlite', 31 | 'database' => 'testing', 32 | 'prefix' => '', 33 | ]); 34 | } 35 | 36 | /** 37 | * Bootstrap the test environment. 38 | **/ 39 | public function setUp() 40 | { 41 | parent::setUp(); 42 | 43 | $this->loadMigrationsFrom([ 44 | '--database' => 'testing', 45 | '--path' => realpath(__DIR__.'/migrations'), 46 | ]); 47 | } 48 | 49 | /** 50 | * Test running migration. 51 | * 52 | * @test 53 | */ 54 | public function testRunningMigration() 55 | { 56 | $users = \DB::table('testbench_users')->where('id', '=', 1)->first(); 57 | $this->assertEquals('hello@orchestraplatform.com', $users->email); 58 | $this->assertTrue(\Hash::check('123', $users->password)); 59 | } 60 | 61 | /** 62 | * Cleanup after the migration has been run. 63 | * 64 | * @author 65 | **/ 66 | public function tearDown() 67 | { 68 | $this->artisan('migrate:rollback'); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Unit/MigrateTest.php: -------------------------------------------------------------------------------- 1 | artisan('migrate', ['--database' => 'testing']); 17 | $users = \DB::table('test_users')->where('id', '=', 1)->first(); 18 | $this->assertEquals('hello@orchestraplatform.com', $users->email); 19 | $this->assertTrue(\Hash::check('123', $users->password)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/migrations/2017_10_02_124618_create_test_tables.php: -------------------------------------------------------------------------------- 1 | increments('id'); 15 | $table->string('email'); 16 | $table->string('password'); 17 | $table->timestamps(); 18 | }); 19 | $now = Carbon::now(); 20 | DB::table('test_users')->insert([ 21 | 'email' => 'hello@db-exporter.com', 22 | 'password' => Hash::make('123'), 23 | 'created_at' => $now, 24 | 'updated_at' => $now, 25 | ]); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | */ 31 | public function down() 32 | { 33 | Schema::drop('test_users'); 34 | } 35 | } 36 | --------------------------------------------------------------------------------