├── .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 | 
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 |
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 |
46 |
47 | @if($migrations->lastPage() > 1)
48 |
49 | {{ $migrations->links() }}
50 |
51 | @endif
52 |
53 |
54 |
55 |
56 |
57 |
65 |
66 |
67 |
68 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
82 |
83 |
84 | Fresh migrations
85 |
86 |
87 |
88 | Are you sure to fresh migrations?
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
99 |
102 |
103 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
127 |
128 |
129 | Migrate the migrations
130 |
131 |
132 |
133 | Are you sure to migrate the migrations?
134 |
135 |
136 | Note: Safe migrate will fresh the database then re-run migrations!
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
147 |
150 |
151 |
154 |
155 |
156 |
157 |
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 |
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 |
59 |
60 |
61 |
66 |
67 |
68 | Delete
69 |
70 |
71 |
72 | Are you sure to delete '{{ $migrationName }}' migration
73 |
74 |
75 |
76 |
77 |
78 |
79 |
82 |
85 |
88 |
89 |
90 |
91 |
92 | |
93 |
94 |
95 |
96 |
97 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
111 |
112 |
113 | Rollback
114 |
115 |
116 |
117 | Are you sure to rollback '{{ $migrationName }}' migration
118 |
119 | @if($batch != $maxBatch)
120 |
121 | Note: Force rollback will change the batch of migration then rollback migration
122 |
123 | @endif
124 |
125 |
126 |
127 |
128 |
129 |
130 | @if($batch === $maxBatch)
131 |
134 | @else
135 |
138 | @endif
139 |
140 |
143 |
144 |
145 |
146 |
147 | |
148 |
149 |
150 |
151 |
152 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | {{ $migrationName }} Structure
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | |
174 | Name
175 | |
176 |
177 | Type
178 | |
179 |
180 | Nullable
181 | |
182 |
183 | Default
184 | |
185 |
186 | Unique
187 | |
188 |
189 |
190 |
191 | @foreach($structure as $item)
192 |
193 | |
194 | {{ $item['name'] }}
195 | |
196 |
197 | {{ $item['type'] }}
198 | |
199 |
200 | @if($item['nullable'])
201 |
202 | Yes
203 |
204 | @endif
205 | |
206 |
207 | {{ $item['default'] }}
208 | |
209 |
210 | @if($item['unique'])
211 |
212 | Yes
213 |
214 | @endif
215 | |
216 |
217 | @endforeach
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 | Preview queries:
227 | @foreach($previewQueries as $query)
228 |
229 | {{ $query }}
230 |
231 | @endforeach
232 |
233 |
234 |
235 |
236 |
237 |
238 |
241 |
242 |
243 |
244 |
245 | |
246 |
247 |
--------------------------------------------------------------------------------