├── .github └── FUNDING.yml ├── routes └── migrator.php ├── resources └── views │ ├── message.blade.php │ ├── layout.blade.php │ └── livewire │ └── migration │ ├── create.blade.php │ ├── read.blade.php │ └── single.blade.php ├── src ├── Http │ ├── Traits │ │ └── Paginate.php │ └── Livewire │ │ └── Migration │ │ ├── Create.php │ │ ├── Single.php │ │ └── Read.php ├── MigratorServiceProvider.php └── Service │ ├── StructureParser.php │ ├── MigratorParser.php │ └── SafeMigrate.php ├── composer.json ├── README.md ├── phpunit.xml └── config └── migrator.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: rezaamini-ir 4 | custom: https://zarinp.al/rezaamini 5 | -------------------------------------------------------------------------------- /routes/migrator.php: -------------------------------------------------------------------------------- 1 | name('migrator.read')->middleware( 8 | !config('migrator.middleware') ? ['web'] : array_unique(array_merge(['web'], (array) config('migrator.middleware'))) 9 | ); 10 | -------------------------------------------------------------------------------- /resources/views/message.blade.php: -------------------------------------------------------------------------------- 1 | @if(session()->has('message')) 2 |
3 | {!! session()->get('message')['message'] !!} 4 |
5 | @endif 6 | -------------------------------------------------------------------------------- /src/Http/Traits/Paginate.php: -------------------------------------------------------------------------------- 1 | forPage($page, $perPage), $items->count(), $perPage, $page, $options); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rezaamini-ir/migrator", 3 | "description": "Migrator is a GUI migration manager for Laravel.", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Reza Amini", 8 | "email": "rezaaminiroyal@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "php": "^7.3|^8.0", 13 | "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", 14 | "livewire/livewire": "^1.0|^2.5|^3.4" 15 | }, 16 | "require-dev": { 17 | "orchestra/testbench": "~3.7|^4.0|^5.0|^6.0|^8.0|^9.0" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "Migrator\\": "src/" 22 | } 23 | }, 24 | "autoload-dev": { 25 | "psr-4": { 26 | "MigratorTest\\": "tests/" 27 | } 28 | }, 29 | "extra": { 30 | "laravel": { 31 | "providers": [ 32 | "Migrator\\MigratorServiceProvider" 33 | ] 34 | } 35 | }, 36 | "minimum-stability": "dev", 37 | "prefer-stable": true 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Migrator 2 | Migrator is a GUI migration manager for Laravel which you can create, manage and delete your migration. 3 | 4 | Also, with Migrator you will be able to use a feature called "Safe Migrate" which allows you to run migration without fear of foreign key sorting, it will automatically run migrations in the correct order and you don't need to change the migrations filename. 5 | 6 | ![Migrator photo](https://user-images.githubusercontent.com/86796762/148734667-b50955b3-e8d8-4a6d-8057-8a1c293eb75a.png) 7 | ## Installation: 8 | 9 | To install Migrator you can execute this command: 10 | ```bash 11 | composer require rezaamini-ir/migrator 12 | ``` 13 | 14 | Then you will access to `/migrator` route. 15 | 16 | ## Config 17 | To access config file you need to publish config files to your project with this command: 18 | ```bash 19 | php artisan vendor:publish --tag=migrator-config 20 | ``` 21 | 22 | Now you will be able to change config as you want! 23 | 24 | To change the route path you can change the `route` key in migrator config. 25 | 26 | And, with `middleware` key you can set your middleware to authenticate your user. 27 | 28 | **If you don't need to authenticate users to access migrator you can set the value to `web`.** 29 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./tests 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/MigratorServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom(__DIR__.'/../config/migrator.php', 'migrator'); 16 | } 17 | 18 | public function boot() 19 | { 20 | $local = $this->app->environment('local'); 21 | $only = config('migrator.local', true); 22 | 23 | if ($local || ! $only) { 24 | $this->loadViewsFrom(__DIR__.'/../resources/views', 'migrator'); 25 | $this->loadRoutesFrom(__DIR__.'/../routes/migrator.php'); 26 | 27 | $this->publishes([ 28 | __DIR__.'/../config' => config_path() 29 | ], 'migrator-config'); 30 | 31 | $this->registerLivewireComponents(); 32 | } 33 | } 34 | 35 | private function registerLivewireComponents() 36 | { 37 | Livewire::component('migrator::livewire.migration.create', Create::class); 38 | Livewire::component('migrator::livewire.migration.read', Read::class); 39 | Livewire::component('migrator::livewire.migration.single', Single::class); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /config/migrator.php: -------------------------------------------------------------------------------- 1 | 'migrator', 11 | 12 | /* 13 | |-------------------------------------------------------------------------- 14 | | Middlewares 15 | | Middlewares used to access the Migrator route 16 | |-------------------------------------------------------------------------- 17 | */ 18 | 'middleware' => ['auth'], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Only on local 23 | | The flag that prevents showing commands if the environment is in production 24 | |-------------------------------------------------------------------------- 25 | */ 26 | 'local' => true, 27 | 28 | /* 29 | |-------------------------------------------------------------------------- 30 | | Pagination 31 | | The number of items per page in pagination 32 | |-------------------------------------------------------------------------- 33 | */ 34 | 'per_page' => 15, 35 | 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | Migrations table name 40 | | Set the name of the migrations table 41 | |-------------------------------------------------------------------------- 42 | */ 43 | 'migrations_table' => 'migrations', 44 | 45 | ]; 46 | -------------------------------------------------------------------------------- /resources/views/layout.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | @livewireStyles 11 | Migrator - {{ $title }} 12 | 13 | 14 | 15 | 16 |

17 | Migrator 18 |

19 | 20 |
21 |
22 | {{ $slot }} 23 |
24 |
25 | @livewireScripts 26 | 27 | 28 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /resources/views/livewire/migration/create.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | @error('name') 6 | {{ $message }} 7 | @enderror 8 |
9 | 10 |
11 | 12 | @error('table') 13 | {{ $message }} 14 | @enderror 15 |
16 | 17 |
18 | 23 | @error('connection') 24 | {{ $message }} 25 | @enderror 26 |
27 | 28 |
29 | 33 | @error('type') 34 | {{ $message }} 35 | @enderror 36 |
37 | 38 |
39 | 40 |
41 |
42 | -------------------------------------------------------------------------------- /src/Http/Livewire/Migration/Create.php: -------------------------------------------------------------------------------- 1 | connection = config('database.default'); 42 | } 43 | 44 | /** 45 | * Create a migration from user input. 46 | */ 47 | public function create() 48 | { 49 | $this->validate(); 50 | 51 | $array = [ 52 | 'name' => strtolower($this->name), 53 | ]; 54 | 55 | if ($this->type == 'edit') { 56 | $array['--table'] = $this->table; 57 | } else { 58 | $array['--create'] = $this->table; 59 | } 60 | 61 | Artisan::call('make:migration', $array); 62 | 63 | if ($this->connection and $this->connection != config('database.default')) { 64 | $this->addConnection(); 65 | } 66 | 67 | $this->dispatchBrowserEvent('show-message', [ 68 | 'type' => 'success', 69 | 'message' => 'Migration was created.' 70 | ]); 71 | 72 | $this->reset('name', 'table'); 73 | $this->emit('migrationUpdated'); 74 | } 75 | 76 | /** 77 | * Render view. 78 | * 79 | * @return View 80 | */ 81 | public function render() 82 | { 83 | $connections = array_keys(config('database.connections')); 84 | 85 | return view('migrator::livewire.migration.create', compact('connections')); 86 | } 87 | 88 | /** 89 | * Alter the migration file to add the databsae connection name. 90 | */ 91 | private function addConnection() 92 | { 93 | $output = Artisan::output(); 94 | 95 | $fileName = trim(substr($output, stripos($output, ":") + 1)) . '.php'; 96 | 97 | $file = database_path('migrations\\'.$fileName); 98 | 99 | $fileContent = file_get_contents($file); 100 | 101 | $position = stripos($fileContent, "extends Migration") + 20; 102 | 103 | $comment = " /**\n * The database connection that should be used by the migration.\n *\n * @var string\n */\n"; 104 | 105 | $finalContent = substr($fileContent, 0, $position) . "\n" . $comment . ' protected $connection = ' . "'" . $this->connection . "';\n\n" . substr($fileContent, $position); 106 | 107 | file_put_contents($file, $finalContent); 108 | } 109 | 110 | /** 111 | * Get migration creation validation rules. 112 | * 113 | * @return array 114 | */ 115 | public function getRules() 116 | { 117 | return [ 118 | 'name' => 'required|min:2', 119 | 'table' => 'required|min:2', 120 | 'connection' => ['required', Rule::in(array_keys(config('database.connections')))], 121 | 'type' => 'required|in:create,edit' 122 | ]; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/Service/StructureParser.php: -------------------------------------------------------------------------------- 1 | matches = $matches; 29 | } 30 | 31 | /** 32 | * Get the structured field list of all matched fields. 33 | * 34 | * @return array 35 | */ 36 | public function getStructure() 37 | { 38 | foreach($this->matches[0] as $match) { 39 | $match = trim(str_replace(';', '', $match)); 40 | 41 | $match = substr($match, strpos($match, '->') + 2); 42 | $type = $this->getType($match); 43 | 44 | $this->structure[] = [ 45 | 'name' => $this->getName($match, $type), 46 | 'type' => $type, 47 | 'nullable' => $this->checkNullable($match), 48 | 'unique' => $this->checkUnique($match), 49 | 'default' => $this->checkDefault($match) 50 | ]; 51 | } 52 | 53 | return array_filter($this->structure, function($value) { 54 | return !is_null($value['type']); 55 | }); 56 | } 57 | 58 | /** 59 | * Check if the field has the unique attribute. 60 | * 61 | * @param string $match Field 62 | * @return false|int 63 | */ 64 | private function checkUnique($match) 65 | { 66 | return strpos($match, '->unique()'); 67 | } 68 | 69 | /** 70 | * Check if the field has a default value. 71 | * 72 | * @param string $match Field 73 | * @return false|string 74 | */ 75 | private function checkDefault($match) 76 | { 77 | if (!strpos($match, '->default(')) { 78 | return false; 79 | } 80 | 81 | preg_match('/->default\((.*?)\).*/', $match, $newMatch); 82 | 83 | return str_replace(['"', '\''], '', $newMatch[1] ?? ''); 84 | } 85 | 86 | /** 87 | * Check if the field has the nullable attribute. 88 | * 89 | * @param string $match 90 | * @return false|int 91 | */ 92 | private function checkNullable($match) 93 | { 94 | return strpos($match, '->nullable()'); 95 | } 96 | 97 | /** 98 | * Get the name of field. 99 | * 100 | * @param string $match 101 | * @param string $type 102 | * @return false|string 103 | */ 104 | private function getName($match, $type) 105 | { 106 | $matches = [ 107 | 'id' => 'id', 108 | 'nullableTimestamps' => 'created_at, updated_at', 109 | 'timestampsTz' => 'created_at, updated_at', 110 | 'timestamps' => 'created_at, updated_at', 111 | 'rememberToken' => 'remember_token', 112 | ]; 113 | 114 | if (in_array($type, array_keys($matches))){ 115 | return $matches[$type]; 116 | } 117 | 118 | if (in_array($type, ['enum', 'set'])){ 119 | return substr($match, stripos($match, "'") + 1, (stripos($match, ",") - stripos($match, "'") - 2)); 120 | } 121 | 122 | if ($type === 'foreignIdFor'){ 123 | return substr($match, stripos($match, "(") + 1, (stripos($match, ")") - stripos($match, "(") - 1)); 124 | } 125 | 126 | return substr($match, stripos($match, "'") + 1, stripos(substr($match, stripos($match, "'") + 1), "'")); 127 | } 128 | 129 | /** 130 | * Get the type of field. 131 | * 132 | * @param string $match 133 | * @return mixed|string 134 | */ 135 | private function getType($match) 136 | { 137 | preg_match("/^(\w+)\(.*\).*/si", $match, $newMatches); 138 | 139 | return $newMatches[1] ?? ''; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Service/MigratorParser.php: -------------------------------------------------------------------------------- 1 | name = $migration->getFilename(); 37 | $this->migration = $migration; 38 | } 39 | 40 | /** 41 | * Get a human-readable name from the migration name. 42 | * i.e. If the migration name is '2014_10_12_000000_create_users_table' 43 | * it will return 'Create users table'. 44 | * 45 | * @return string 46 | */ 47 | public function getName() 48 | { 49 | $name = $this->name; 50 | 51 | preg_match('/\d+_\d+_\d+_\d+_(\w+)/', $name, $m); 52 | 53 | $name = Str::ucfirst(Str::replace('_', ' ', $m[1])); 54 | 55 | return $name; 56 | } 57 | 58 | /** 59 | * Get the migration creation date difference from today in a human-readable format. 60 | * 61 | * @return string 62 | */ 63 | public function getDate() 64 | { 65 | $date = $this->name; 66 | 67 | preg_match('/([\d+\_]+)/', $date, $m); 68 | 69 | $date = Str::replace('_', ' ', $m[1]); 70 | 71 | return Carbon::createFromFormat('Y m d His ', $date)->ago(); 72 | } 73 | 74 | /** 75 | * Get the migration connection name. 76 | * 77 | * @return string 78 | */ 79 | public function getConnectionName() 80 | { 81 | $file = $this->migration->getPathname(); 82 | $migrationObject = (function () use ($file) { 83 | return $this->resolvePath($file); 84 | })->call(app('migrator')); 85 | 86 | return $migrationObject->getConnection() ?: config('database.default'); 87 | } 88 | 89 | /** 90 | * Get structure content of migration. 91 | * 92 | * @return array 93 | */ 94 | public function getStructure() 95 | { 96 | $contents = $this->migration->getContents(); 97 | 98 | preg_match('/Schema::.+(?:\n+)?function\s?\(.*?\$(\w+)/mi', $contents, $m); 99 | 100 | $tableName = $m[1] ?? 'table'; 101 | 102 | $searchForOne = '$'.$tableName.'->'; 103 | 104 | $patternOne = preg_quote($searchForOne, '/'); 105 | 106 | $patternOne = "/^.*$patternOne.*\$/m"; 107 | 108 | preg_match_all($patternOne, $contents, $matches); 109 | 110 | try { 111 | $structure = new StructureParser($matches); 112 | 113 | return $structure->getStructure(); 114 | } catch (\Exception $exception){ 115 | return []; 116 | } 117 | 118 | } 119 | 120 | public function getPreview($migration, $method = 'up') 121 | { 122 | try { 123 | $migrator = app('migrator'); 124 | 125 | $filesystem = resolve(Filesystem::class); 126 | 127 | $filePath = $filesystem->path($migration); 128 | require_once $filePath; 129 | 130 | $db = $migrator->resolveConnection( 131 | $migrationObject->getConnection() 132 | ); 133 | 134 | $queries = $db->pretend(function () use ($db, $migrationObject, $method) { 135 | if (method_exists($migrationObject, $method)) { 136 | $resolver = resolve(Resolver::class); 137 | $previousConnection = $resolver->getDefaultConnection(); 138 | 139 | try { 140 | $resolver->setDefaultConnection($db->getName()); 141 | 142 | $migrationObject->{$method}(); 143 | } finally { 144 | $resolver->setDefaultConnection($previousConnection); 145 | } 146 | } 147 | }); 148 | 149 | return array_column($queries, 'query'); 150 | } catch (\Exception $exception){ 151 | return []; 152 | } 153 | 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/Service/SafeMigrate.php: -------------------------------------------------------------------------------- 1 | table = $this->renderTableName($error); 39 | } 40 | 41 | /** 42 | * Extract table name from SQL foreign key constraint error. 43 | * 44 | * @param $error 45 | * @return mixed|string 46 | */ 47 | public function renderTableName($error) 48 | { 49 | preg_match("/.*references `(\w+)`.*/", $error, $match); 50 | 51 | return $match[1] ?? ''; 52 | } 53 | 54 | /** 55 | * Get the list of migration files with the target table name from the migrations directory. 56 | * 57 | * @return array 58 | */ 59 | public function getMigrationFiles() 60 | { 61 | $migrations = File::glob(database_path("migrations".DIRECTORY_SEPARATOR."*{$this->table}*")); 62 | 63 | return [$this->table => $migrations]; 64 | } 65 | 66 | /** 67 | * Get the safe-to-run list of migrations. 68 | * ! Warning: This function will wipe the database. Be careful on usage. 69 | * 70 | * This will run run and sort 71 | * @return array|string 72 | */ 73 | public function getMigrations() 74 | { 75 | $migrations = $this->getMigrationFiles(); 76 | $this->migrations[] = $migrations; 77 | 78 | if (is_null($migrations)){ 79 | return "No dependency founded for `{$this->table}` table"; 80 | } 81 | 82 | $this->clearDatabase(); 83 | 84 | foreach ($migrations as $table => $migration) { 85 | if ($migration == []){ 86 | continue; 87 | } 88 | 89 | try { 90 | $this->runMigration($migration); 91 | } catch (\Exception $exception){ 92 | if (\Str::contains($exception->getMessage(), 'errno: 150')){ 93 | $this->table = $this->renderTableName($exception->getMessage()); 94 | $this->getMigrations(); 95 | } else { 96 | return "There was an error: {$exception->getMessage()}"; 97 | } 98 | } 99 | } 100 | 101 | $this->clearDatabase(); 102 | 103 | krsort($this->migrations); 104 | 105 | return $this->migrations; 106 | } 107 | 108 | /** 109 | * Run the ordered migrations of target table. 110 | * We will also call the normal migrate since we have previously wiped the database. 111 | * 112 | * @return array 113 | */ 114 | public function execute() 115 | { 116 | $migrations = $this->getMigrations(); 117 | 118 | $message = "Start safe migrate: \n"; 119 | foreach ($migrations as $migration) { 120 | $pathArray = $migration[array_key_first($migration)]; 121 | if ($pathArray == []){ 122 | return [ 123 | 'message' => "Dependencies for `".array_key_first($migration)."` table not found!", 124 | 'type' => 'error' 125 | ]; 126 | } 127 | 128 | try { 129 | $message .= $this->runMigration($pathArray); 130 | } catch (\Exception $exception){ 131 | return [ 132 | 'message' => "There is an error: {$exception->getMessage()}", 133 | 'type' => 'error' 134 | ]; 135 | } 136 | } 137 | 138 | \Artisan::call('migrate'); 139 | 140 | $message .= \Artisan::output(); 141 | 142 | return [ 143 | 'message' => $message, 144 | 'type' => 'success' 145 | ]; 146 | } 147 | 148 | /** 149 | * Run migrations. 150 | * 151 | * @param string $path 152 | * @return string 153 | */ 154 | private function runMigration($path){ 155 | \Artisan::call('migrate', [ 156 | '--path' => $path, 157 | '--realpath' => true, 158 | ]); 159 | 160 | return Artisan::output(); 161 | } 162 | 163 | /** 164 | * Clean the database. Drop all tables, views, and types. 165 | */ 166 | private function clearDatabase() 167 | { 168 | \Artisan::call('db:wipe', [ 169 | '--force' => true 170 | ]); 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /src/Http/Livewire/Migration/Single.php: -------------------------------------------------------------------------------- 1 | migrationPath = $migration->getPathname(); 66 | $this->migrationFile = $migration->getFilename(); 67 | $migratorParser = new MigratorParser($migration); 68 | $this->migrationName = $migratorParser->getName(); 69 | $this->migrationConnectionName = $migratorParser->getConnectionName(); 70 | $this->migrationCreatedAt = $migratorParser->getDate(); 71 | $this->batch = DB::table(config('migrator.migrations_table')) 72 | ->where('migration', str_replace('.php', '', $this->migrationFile)) 73 | ->first(['batch'])->batch ?? 0; 74 | $this->structure = $migratorParser->getStructure(); 75 | $this->previewQueries = $migratorParser->getPreview($migration->getPathname()); 76 | } 77 | 78 | /** 79 | * Run migration command. 80 | */ 81 | public function migrate() 82 | { 83 | try { 84 | \Artisan::call('migrate', [ 85 | '--path' => $this->getPath() 86 | ]); 87 | 88 | $message = 'Migration was migrated.'; 89 | $type = 'success'; 90 | } catch(\Exception $exception) { 91 | $message = $exception->getMessage(); 92 | $type = 'error'; 93 | } 94 | 95 | $this->dispatchBrowserEvent('show-message', [ 96 | 'type' => $type, 97 | 'message' => Str::replace("\n", '
', $message) 98 | ]); 99 | 100 | $this->emit('migrationUpdated'); 101 | } 102 | 103 | /** 104 | * Refresh migrations. 105 | */ 106 | public function refresh() 107 | { 108 | \Artisan::call('migrate:refresh', [ 109 | '--path' => $this->getPath() 110 | ]); 111 | 112 | $this->dispatchBrowserEvent('show-message', [ 113 | 'type' => 'success', 114 | 'message' => 'Migration was refreshed.' 115 | ]); 116 | 117 | $this->emit('migrationUpdated'); 118 | } 119 | 120 | /** 121 | * Roll back all database migrations. 122 | */ 123 | public function removeTable() 124 | { 125 | \Artisan::call('migrate:reset', [ 126 | '--path' => $this->getPath(), 127 | '--force' => true, 128 | ]); 129 | 130 | $this->dispatchBrowserEvent('show-message', [ 131 | 'type' => 'success', 132 | 'message' => 'Table was dropped.' 133 | ]); 134 | 135 | $this->emit('migrationUpdated'); 136 | } 137 | 138 | /** 139 | * Delete a migration. 140 | */ 141 | public function deleteMigration() 142 | { 143 | $this->removeTable(); 144 | 145 | File::delete($this->migrationPath); 146 | 147 | $this->emit('migrationUpdated'); 148 | } 149 | 150 | /** 151 | * Roll back a specific migration. 152 | */ 153 | public function rollback() 154 | { 155 | $migrationTable = config('migrator.migrations_table'); 156 | \DB::table($migrationTable) 157 | ->where('migration', str_replace('.php', '', $this->migrationFile)) 158 | ->update(['batch' => \DB::table($migrationTable)->max('batch')]); 159 | 160 | try { 161 | \Artisan::call('migrate:rollback', [ 162 | '--path' => $this->getPath(), 163 | ]); 164 | 165 | $message = 'Migration was rolled back.'; 166 | $type = 'success'; 167 | } catch(\Exception $exception) { 168 | $message = $exception->getMessage(); 169 | $type = 'error'; 170 | } 171 | 172 | $this->dispatchBrowserEvent('show-message', [ 173 | 'type' => $type, 174 | 'message' => Str::replace("\n", '
', $message) 175 | ]); 176 | 177 | $this->emit('migrationUpdated'); 178 | } 179 | 180 | /** 181 | * Render the page. 182 | * 183 | * @return View 184 | */ 185 | public function render() 186 | { 187 | return view('migrator::livewire.migration.single'); 188 | } 189 | 190 | /** 191 | * Get the migration path. 192 | * 193 | * @return string 194 | */ 195 | private function getPath() 196 | { 197 | return str_replace(base_path(), '', $this->migrationPath); 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /src/Http/Livewire/Migration/Read.php: -------------------------------------------------------------------------------- 1 | getMessage(), 'errno: 150')){ 56 | $safeMigrator = (new SafeMigrate($exception->getMessage()))->execute(); 57 | $output = $safeMigrator['message']; 58 | $type = $safeMigrator['type']; 59 | } else { 60 | $output = $exception->getMessage(); 61 | $type = 'error'; 62 | } 63 | } 64 | 65 | $this->storeMessage($output, $type); 66 | 67 | $this->redirect(route('migrator.read')); 68 | } 69 | 70 | /** 71 | * Drop all tables and rerun the migrations. 72 | * You can also optionally run the seeds. 73 | * 74 | * @param bool $withSeed 75 | */ 76 | public function fresh($withSeed = false) 77 | { 78 | $args = $withSeed ? ['--seed' => true] : []; 79 | 80 | try{ 81 | Artisan::call('migrate:fresh', $args); 82 | $output = Artisan::output(); 83 | $type = 'success'; 84 | } catch (\Exception $exception){ 85 | $output = $exception->getMessage(); 86 | $type = 'error'; 87 | } 88 | 89 | $this->storeMessage($output, $type); 90 | 91 | $this->redirect(route('migrator.read')); 92 | } 93 | 94 | /** 95 | * Render Page. 96 | * 97 | * @return mixed 98 | */ 99 | public function render() 100 | { 101 | if (!Schema::hasTable(config('migrator.migrations_table'))){ 102 | Artisan::call('migrate:install'); 103 | } 104 | 105 | $migrations = $this->getMigrationsForView(); 106 | $migrations = $this->filterMigrationsBySearchValue($migrations); 107 | $migrations = $this->withPaginate($migrations); 108 | 109 | return view('migrator::livewire.migration.read', ['migrations' => $migrations]) 110 | ->layout('migrator::layout', ['title' => 'Migration List']); 111 | } 112 | 113 | /** 114 | * Flash the messages into the session. 115 | * 116 | * @param string $output 117 | * @param string $type 118 | */ 119 | private function storeMessage(string $output, string $type) 120 | { 121 | session()->flash('message', [ 122 | 'message' => Str::replace("\n", '
', $output), 123 | 'type' => $type 124 | ]); 125 | } 126 | 127 | /** 128 | * Get the list of all migration paths, including the custom migration directories. 129 | * 130 | * @return array 131 | */ 132 | private static function migrationDirs() 133 | { 134 | $migrationDirs = []; 135 | $migrationDirs[] = app()->databasePath().DIRECTORY_SEPARATOR.'migrations'; 136 | 137 | foreach (app('migrator')->paths() as $path) { 138 | $migrationDirs[] = $path; 139 | } 140 | 141 | return $migrationDirs; 142 | } 143 | 144 | /** 145 | * Wrapper to Paginate the input data. 146 | * 147 | * @param array|Collection $data 148 | * @return LengthAwarePaginator 149 | */ 150 | private function withPaginate($data) 151 | { 152 | $perPage = config('migrator.per_page', 10); 153 | $path = config('migrator.route', 'migrator'); 154 | 155 | return $this->paginate($data, $perPage)->withPath($path); 156 | } 157 | 158 | /** 159 | * Get the list of all migrations in the app, including those in the custom migration directories. 160 | * 161 | * @return array 162 | */ 163 | private function getMigrationsForView() 164 | { 165 | $migrations = []; 166 | 167 | foreach (self::migrationDirs() as $dir) { 168 | $migrations = array_merge(File::files($dir), $migrations); 169 | } 170 | 171 | return $migrations; 172 | } 173 | 174 | /* 175 | * filter migrations by search value 176 | * 177 | * @return array 178 | */ 179 | public function filterMigrationsBySearchValue($migrations) : array 180 | { 181 | if (!empty($this->search)){ 182 | return collect($migrations)->filter(function ($migration) { 183 | $migratorParser = resolve(MigratorParser::class , ['migration' => $migration]); 184 | if (Str::contains($migratorParser->getName(),$this->search) ){ 185 | return $migration; 186 | } 187 | })->toArray(); 188 | } 189 | return $migrations; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /resources/views/livewire/migration/read.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | @include('migrator::message') 4 | 5 | @livewire('migrator::livewire.migration.create') 6 | 7 |
8 |
9 |
10 |
11 | 12 | 13 | 14 | 17 | 20 | 23 | 26 | 30 | 31 | 32 | 33 | @forelse($migrations as $key => $migration) 34 | @livewire('migrator::livewire.migration.single', ['migration' => $migration], key($key)) 35 | @empty 36 | 37 | 42 | 43 | @endforelse 44 | 45 |
15 | Name 16 | 18 | Connection 19 | 21 | Batch 22 | 24 | Created At 25 | 27 | Action 28 | 29 |
38 |
39 | No migration found. 40 |
41 |
46 | 47 | @if($migrations->lastPage() > 1) 48 |
49 | {{ $migrations->links() }} 50 |
51 | @endif 52 |
53 |
54 |
55 |
56 | 57 |
58 |
59 | 60 |
61 |
62 | 63 |
64 |
65 | 66 | 110 | 111 | 158 | 159 |
160 | Migrator is a GUI migration manager for Laravel. 161 |
162 |
163 | -------------------------------------------------------------------------------- /resources/views/livewire/migration/single.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @php 3 | $migrationData = DB::table(config('migrator.migrations_table'))->where('migration', str_replace('.php', '', $migrationFile)); 4 | $exists = $migrationData->exists(); 5 | $maxBatch = DB::table(config('migrator.migrations_table'))->max('batch'); 6 | @endphp 7 | 8 |
9 | {{ $migrationName }} 10 |
11 | 12 | 13 | 14 |
{{ $migrationConnectionName }}
15 | 16 | 17 | 18 | @if($exists) 19 | 20 | {{ $migrationData->first()->batch }} 21 | 22 | @else 23 | 24 | Absent 25 | 26 | @endif 27 | 28 | 29 | 30 | {{ $migrationCreatedAt }} 31 | 32 | 33 | 34 | @if($exists) 35 | Refresh 36 | @else 37 | Migrate 38 | @endif 39 | 40 | Delete 41 | 42 | @if($exists) 43 | Rollback 44 | @else 45 | Rollback 46 | @endif 47 | 48 | 49 | 50 |
51 |
52 | 55 | 56 | 57 | 58 | 90 |
91 |
92 | 93 | 94 | 95 |
96 |
97 | 100 | 101 | 102 | 103 | 145 |
146 |
147 | 148 | 149 | 150 |
151 |
152 | 155 | 156 | 157 | 158 | 243 |
244 |
245 | 246 | 247 | --------------------------------------------------------------------------------