├── LICENSE ├── README.md ├── composer.json ├── config └── smooth.php └── src ├── Abstracts └── Schema.php ├── Console ├── MigrateMakeCommand.php ├── SmoothCreateCommand.php └── stubs │ └── Schema.stub ├── Definition.php ├── Helpers ├── Constants.php ├── SchemaComposer.php ├── SchemaReader.php ├── SchemaWriter.php └── stubs │ ├── blank.stub │ ├── create.stub │ ├── schema_table.stub │ └── update.stub ├── Providers ├── MakeMigrationProvider.php └── SmoothMigrationProvider.php ├── Repositories └── SmoothMigrationRepository.php └── Traits └── SmoothMigratable.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Gabriel Nwogu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smooth Migrations 2 | A Flexible way to manage Laravel Migrations 3 | Smooth Migration is a Laravel package that allows you abstract your schema definitions from your migration files. 4 | This allows you to do two simple things really well. 5 | * 1. Specify which schema to run first. Very useful for situations where you need foreign key references without 6 | needing to change migration file name. 7 | * 2. Update your schema on the go. Need to drop a column? Add a new column? No need to generate migration files for that. 8 | Just update the existing schema definition and the package generates the necessary migration files 9 | 10 | Smooth Migrations handles the generation of all migration files with their appropriate methods. 11 | 12 | ## Installation. 13 | 14 | Install via composer: 15 | 16 | ```composer require nwogu/smooth-migration``` 17 | 18 | ## Schema Folder: 19 | 20 | If you'd like to change the default schema directory, publish the config files with the 21 | ```php artisan vendor:publish``` command 22 | 23 | The default schema directory is in database/schemas 24 | 25 | ## Usage: 26 | 27 | Create a schema file for your table with the command and pass it the table name: 28 | ```php artisan smooth:create product``` 29 | 30 | This will create a schema template for you to define your migrations: 31 | ``` 32 | name = "string"; 64 | $definition->is_active = "integer|default:true"; 65 | $definition->description = "text|nullable"; 66 | $definition->fileable = "morphs"; 67 | $definition->user_id = "integer|on:users|onDelete:cascade"; 68 | } 69 | 70 | } 71 | ``` 72 | ### Schema Definition Syntax. 73 | The syntax to define schemas is similar to writing laravel validation rules. 74 | Each \Nwogu\SmoothMigration\Definition property is a column name. 75 | Each string definition contains valid \Illuminate\Database\Schema\Blueprint methods with parmeters 76 | seperated by a ":" 77 | 78 | To generate the migrations from the schemas, call the ```php artisan make:migration -s|smooth``` command 79 | 80 | This command will generate the appropriate migration files. 81 | 82 | ## Supported Schema Definitions 83 | 84 | ``` 85 | $definition->id = "bigIncrements"; 86 | $definition->votes = "bigInteger"; 87 | $definition->data = "binary"; 88 | $definition->confirmed = "boolean"; 89 | $definition->name = "char:100"; 90 | $definition->created_at = "date"; 91 | $definition->created_at = "dateTime"; 92 | $definition->amount = "decimal:8,2"; 93 | $definition->amount = "double:8,2"; 94 | $definition->amount = "float:8,2"; 95 | $definition->positions = "geometry"; 96 | $definition->positions = "geometryCollection"; 97 | $definition->votes = "integer"; 98 | $definition->visitor = "ipAddress"; 99 | $definition->options = "json"; 100 | $definition->options = "jsonb"; 101 | $definition->positions = "lineString"; 102 | $definition->description = "longText"; 103 | $definition->device = "macAddress"; 104 | $definition->id = "mediumIncrements"; 105 | $definition->votes = "mediumInteger"; 106 | $definition->description = "mediumText"; 107 | $definition->taggable = "morphs"; 108 | $definition->positions = "multiLineString"; 109 | $definition->positions = "multiPoint"; 110 | $definition->positions = "multiPolygon"; 111 | $definition->taggable = "nullableMorphs"; 112 | $definition->position = "point"; 113 | $definition->positions = "polygon"; 114 | $definition->rememberToken = "rememberToken"; 115 | $definition->id = "smallIncrements"; 116 | $definition->votes = "smallInteger"; 117 | $definition->softDeletes = "softDeletes"; 118 | $definition->name = "string:100"; 119 | $definition->description = "text"; 120 | $definition->sunrise = "time"; 121 | $definition->sunrise = "timeTz"; 122 | $definition->added_on = "timestamp"; 123 | $definition->added_on = "timestampTz"; 124 | $definition->id = "tinyIncrements"; 125 | $definition->votes = "tinyInteger"; 126 | $definition->votes = "unsignedBigInteger"; 127 | $definition->amount = "unsignedDecimal:8,2"; 128 | $definition->votes = "unsignedInteger"; 129 | $definition->votes = "unsignedMediumInteger"; 130 | $definition->votes = "unsignedSmallInteger"; 131 | $definition->votes = "unsignedTinyInteger"; 132 | $definition->id = "uuid"; 133 | $definition->birth_year = "year"; 134 | ``` 135 | ## Id Field and Timestamps 136 | You can specify which method your id field should take on. idfield is "increments" by default, 137 | keep in mind that a bigInteger field isn't compatible with an integer field, when specifying 138 | the foreign key definition 139 | You can disable timestamps by specifying the property in your schema class. timestamps are added by default 140 | 141 | ``` 142 | class ProductSchema extends Schema 143 | { 144 | /** 145 | * Auto Incrementing Id 146 | * @var string 147 | */ 148 | protected $idField = "bigIncrements"; 149 | 150 | /** 151 | * Add Timestamps 152 | * @var bool 153 | */ 154 | protected $timestamps = false; 155 | 156 | ``` 157 | Foreign key definition 158 | 159 | ``` 160 | class OutletProductSchema extends Schema 161 | { 162 | ... 163 | 164 | /** 165 | * Schema Definitions 166 | * @param \Nwogu\SmoothMigration\Definition 167 | */ 168 | protected function define(Definition $definition) 169 | { 170 | $definition->product_id = "bigInteger|on:products|onDelete:cascade"; 171 | } 172 | 173 | ``` 174 | ## Running Migrations in Order 175 | 176 | You can specify which schema to run first with the ```runFirst``` property 177 | 178 | ``` 179 | class ProductSchema extends Schema 180 | { 181 | /** 182 | * Specify which schema should run first 183 | * @var array 184 | */ 185 | protected $runFirst = [ 186 | OutletSchema::class, 187 | TaxSchema::class 188 | ]; 189 | 190 | ``` 191 | However, you can't specify a ```runFirst``` for a schema if already defined in the parent schema, take the example above: 192 | The runFirst property of OutletSchema Class or TaxSchema Class cannot contain the ProductSchema Class. 193 | 194 | ## Updating Schemas 195 | 196 | Make changes to your schema class directly. 197 | 198 | Run the ```php artisan make: migration -s``` command, this will generate the appropriate migration files. 199 | 200 | To run your migrations: 201 | ```php artisan migrate``` 202 | 203 | ## Correcting Defined Migrations 204 | 205 | You may have generated a migration and realized you made a mistake. Instead of generating a new 206 | migration file, you can pass the ```--correct``` flag to adjust the last made migration file. 207 | 208 | Eg: ```php artisan make:migration --smooth --correct=product``` 209 | This will compare the current changes with the changes you made before your last. 210 | You can also specify how far back the comparing should go by adding ```.{step}``` to the table name. 211 | Eg: ```php artisan make:migration --smooth --correct=product.2``` 212 | 213 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nwogu/smooth-migration", 3 | "description": "Flexible way to manage Laravel Migrations", 4 | "authors": [ 5 | { 6 | "name": "Gabriel Nwogu", 7 | "email": "nwogugabriel@gmail.com" 8 | } 9 | ], 10 | "license": "MIT", 11 | "keywords": ["laravel", "migration", "update migrations", "manage migrations", "artisan migrate"], 12 | "require": { 13 | "laravel/framework": "^5.6" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "Nwogu\\SmoothMigration\\": "src/" 18 | } 19 | }, 20 | "extra": { 21 | "laravel": { 22 | "providers": [ 23 | "Nwogu\\SmoothMigration\\Providers\\SmoothMigrationProvider", 24 | "Nwogu\\SmoothMigration\\Providers\\MakeMigrationProvider" 25 | ] 26 | } 27 | }, 28 | "minimum-stability": "dev", 29 | "prefer-stable": true 30 | } 31 | -------------------------------------------------------------------------------- /config/smooth.php: -------------------------------------------------------------------------------- 1 | database_path("schema/"), 5 | ]; -------------------------------------------------------------------------------- /src/Abstracts/Schema.php: -------------------------------------------------------------------------------- 1 | table; 43 | } 44 | 45 | /** 46 | * Schema Reader 47 | * @var SchemaReader 48 | */ 49 | protected $reader; 50 | 51 | /** 52 | * Get Schemas To Run First 53 | * @return array 54 | */ 55 | public function runFirst() 56 | { 57 | return $this->runFirst; 58 | } 59 | 60 | /** 61 | * Get Table Schema 62 | * @return string 63 | */ 64 | public function schemas() 65 | { 66 | $definition = new Definition; 67 | 68 | ! $this->idField ?: $definition->id = $this->idField; 69 | 70 | $this->define($definition); 71 | 72 | ! $this->timestamps ?: $definition->{Constants::TIMESTAMP} = Constants::TIMESTAMP; 73 | 74 | return get_object_vars($definition); 75 | } 76 | 77 | /** 78 | * Get Current Schema Load for Migration 79 | * @return array 80 | */ 81 | public function currentSchemaLoad() 82 | { 83 | $serializeLoad['table'] = $this->table(); 84 | 85 | $serializeLoad['schemas'] = $this->schemas(); 86 | 87 | return $serializeLoad; 88 | } 89 | 90 | /** 91 | * Check if Migration Schema is Changed 92 | * @return bool 93 | */ 94 | public function hasChanged() 95 | { 96 | return $this->reader->hasChanged(); 97 | } 98 | 99 | /** 100 | * Get The active reader of Schema 101 | * @return SchemaReader 102 | */ 103 | public function reader() 104 | { 105 | return $this->reader; 106 | } 107 | 108 | /** 109 | * Set the active reader 110 | * @param \Nwogu\SmoothMigration\Helpers\SchemaReader 111 | * 112 | * @return void 113 | */ 114 | public function setReader(SchemaReader $reader) 115 | { 116 | $this->reader = $reader; 117 | } 118 | 119 | /** 120 | * Returns Class Name 121 | * @return string 122 | */ 123 | public function className() 124 | { 125 | return static::class; 126 | } 127 | 128 | /** 129 | * Schema Definitions 130 | */ 131 | abstract protected function define(Definition $definition); 132 | } -------------------------------------------------------------------------------- /src/Console/MigrateMakeCommand.php: -------------------------------------------------------------------------------- 1 | makeDependencies(); 42 | 43 | $this->modifySignature(); 44 | 45 | parent::__construct($creator, $composer); 46 | 47 | } 48 | 49 | /** 50 | * Execute the console command. 51 | * 52 | * @return void 53 | */ 54 | public function handle() 55 | { 56 | $this->useSmoothMigrator() ? 57 | 58 | $this->writeSmoothMigration() : 59 | 60 | $this->parentHandle(); 61 | } 62 | 63 | /** 64 | * Determine if a smooth migration should be created. 65 | * 66 | * @return bool 67 | */ 68 | protected function useSmoothMigrator() 69 | { 70 | return $this->input->hasOption('smooth') && $this->option('smooth'); 71 | } 72 | 73 | /** 74 | * Determine if a smooth migration should be corrected. 75 | * @param Schema $schema 76 | * 77 | * @return bool 78 | */ 79 | protected function shouldCorrectMigration(Schema $schema) 80 | { 81 | return $this->hasCorrectionBeenRequested() 82 | ? $this->isCorrectionForSchema($schema) 83 | : $this->hasCorrectionBeenRequested(); 84 | } 85 | 86 | /** 87 | * Determine if a smooth migration correction has been requested. 88 | * 89 | * @return bool 90 | */ 91 | protected function hasCorrectionBeenRequested() 92 | { 93 | return $this->input->hasOption('correct') && $this->option('correct'); 94 | } 95 | 96 | /** 97 | * Determine if a corection is meant for a schema. 98 | * 99 | * @return bool 100 | */ 101 | protected function isCorrectionForSchema(Schema $schema) 102 | { 103 | return $schema->className() == $this->transformOptionToSchemaClassName(); 104 | } 105 | 106 | /** 107 | * Parse option for correction 108 | * @return string $table 109 | */ 110 | protected function parseCorrectionOption() 111 | { 112 | return explode(".", $this->option('correct'))[0]; 113 | } 114 | 115 | /** 116 | * Transform table to schema class name 117 | * @return string 118 | */ 119 | protected function transformOptionToSchemaClassName() 120 | { 121 | return \Illuminate\Support\Str::studly( 122 | $this->parseCorrectionOption() 123 | . Constants::SMOOTH_SCHEMA_FILE 124 | ); 125 | } 126 | 127 | /** 128 | * Create Laravel's Default Migration Files 129 | * @return void 130 | */ 131 | protected function writeSmoothMigration() 132 | { 133 | 134 | foreach ($this->getSchemaFiles() as $path) { 135 | 136 | $instance = $this->schemaInstance($path); 137 | 138 | $this->runFirstMigrations($instance); 139 | $this->writeNewSmoothMigration($instance); 140 | } 141 | } 142 | 143 | /** 144 | * Get all of the smooth schema files in the smooth schema path. 145 | * 146 | * @return array 147 | */ 148 | protected function getSchemaFiles() 149 | { 150 | return Collection::make($this->schemaDirectory())->flatMap(function ($path) { 151 | return $this->files->glob($path.'*Schema.php*'); 152 | })->filter()->sortBy(function ($file) { 153 | return $this->getSchemaName($file); 154 | })->values()->all(); 155 | } 156 | 157 | /** 158 | * Run First Migrations 159 | * @param Schema $schema 160 | * @return void 161 | */ 162 | protected function runFirstMigrations(Schema $schema) 163 | { 164 | foreach ($schema->runFirst() as $firstRun) { 165 | 166 | $instance = $this->schemaInstance($firstRun); 167 | 168 | $schemaClass = $schema->className(); 169 | 170 | if (in_array($schemaClass, $instance->runFirst())) { 171 | $this->error( 172 | "Circular Reference Error, {$schemaClass} specified to run first in $firstRun"); 173 | exit(); 174 | } 175 | 176 | $this->writeNewSmoothMigration($instance); 177 | } 178 | } 179 | 180 | /** 181 | * Writes a migration to file 182 | * @param Schema $instance 183 | * @return void 184 | */ 185 | protected function writeNewSmoothMigration(Schema $instance) 186 | { 187 | if (in_array($instance->className(), $this->ran)) return; 188 | 189 | $schemaClass = $instance->className(); 190 | 191 | $this->readSchema($instance); 192 | 193 | if ($instance->hasChanged()) { 194 | $this->info("Writing Migration For {$schemaClass}"); 195 | 196 | try { 197 | $migrationPath = $this->writeSchema($instance); 198 | } 199 | catch (\Exception $e) { 200 | $this->error($e->getMessage()); 201 | exit(); 202 | } 203 | 204 | $this->printChangeLog($instance->reader()->changelogs()); 205 | 206 | $this->info("Migration for {$schemaClass} Created Successfully"); 207 | $this->info("Updating Log..."); 208 | 209 | $this->repository->log( 210 | $schemaClass, 211 | $this->fetchSerializableData($instance), 212 | $migrationPath, 213 | $this->repository->getNextBatchNumber( 214 | $schemaClass 215 | ) 216 | ); 217 | 218 | $this->info("Log for {$schemaClass} Updated Successfully"); 219 | } else { 220 | $this->info("No Schema Change Detected For {$schemaClass}"); 221 | } 222 | 223 | array_push($this->ran, $instance->className()); 224 | } 225 | 226 | /** 227 | * Read Schema Changes 228 | * @param Schema $instance 229 | * @return void 230 | */ 231 | protected function readSchema(Schema $instance) 232 | { 233 | $previousSchemaload = $this->repository->previousSchemaLoad( 234 | $instance->className(), 235 | $this->getBatch($instance)); 236 | 237 | $schemaReader = new SchemaReader( 238 | $previousSchemaload, $instance->currentSchemaLoad()); 239 | $instance->setReader($schemaReader); 240 | } 241 | 242 | /** 243 | * Get smooth migration batch 244 | * @param Schema $instance 245 | * @return int $batch 246 | */ 247 | protected function getBatch(Schema $instance) 248 | { 249 | return $this->shouldCorrectMigration($instance) 250 | ? $this->repository->reduceBatchNumber($instance) 251 | : $this->repository->getLastBatchNumber($instance->className()); 252 | } 253 | 254 | /** 255 | * Parse batch number from option 256 | * @return int $batch 257 | */ 258 | protected function parseBatchOption() 259 | { 260 | return explode(".", $this->option('correct'))[1] ?? 1; 261 | } 262 | 263 | /** 264 | * Reduce batch number 265 | * @param Schema $instance 266 | * @return int 267 | */ 268 | protected function reduceBatchNumber(Schema $instance) 269 | { 270 | $batchNumber = $this->repository->getLastBatchNumber($instance->className()) - 271 | $this->parseBatchOption(); 272 | return $batchNumber < 0 ? 0 : $batchNumber; 273 | } 274 | 275 | /** 276 | * Write Migration File 277 | * @param Schema $schemaInstance 278 | * @return string $migrationPath 279 | */ 280 | protected function writeSchema(Schema $schemaInstance) 281 | { 282 | $writer = new SchemaWriter($schemaInstance, $this->runCount); 283 | 284 | $schemaClass = $schemaInstance->className(); 285 | 286 | $migrationPath = $this->shouldCorrectMigration($schemaInstance) 287 | ? $this->getMigrationPathForCorrection($schemaClass, $writer) 288 | : $writer->migrationPath(); 289 | 290 | $this->createFile( 291 | $writer->migrationDirectory(), 292 | $migrationPath, 293 | $writer->write() 294 | ); 295 | 296 | $this->runCount++; 297 | 298 | return $migrationPath; 299 | } 300 | 301 | /** 302 | * Get migration path for correction 303 | * @param string $schemaClass 304 | * @param SchemaWriter $writer 305 | * 306 | * @return string $migrationPath 307 | */ 308 | protected function getMigrationPathForCorrection(string $schemaClass, SchemaWriter $writer) 309 | { 310 | $migrationPath = $this->repository->getLastMigration($schemaClass); 311 | $writer->makeMigrationClass( 312 | $this->getMigrationClassNameFromPath($migrationPath) 313 | ); 314 | return $migrationPath; 315 | } 316 | 317 | /** 318 | * Modify Signature 319 | * @return void 320 | */ 321 | protected function modifySignature() 322 | { 323 | $this->signature = str_replace("{name", "{name='*'", $this->signature); 324 | 325 | $this->signature .= "{--s|smooth : Create a migration file from a Smooth Schema Class.}"; 326 | 327 | $this->signature .= "{--c|correct= : Correct a migration file that has already run.}"; 328 | } 329 | 330 | /** 331 | * Check for the name argument before calling parent 332 | * @return void 333 | */ 334 | protected function parentHandle() 335 | { 336 | return $this->argument("name") == "'*'" ? 337 | 338 | $this->error("Name Argument is Required") : 339 | 340 | parent::handle(); 341 | } 342 | 343 | /** 344 | * Print Change Logs To Terminal 345 | * @param array $changelogs 346 | * @return void 347 | */ 348 | protected function printChangeLog(array $changelogs) 349 | { 350 | foreach ($changelogs as $log) { 351 | $this->info($log); 352 | } 353 | } 354 | 355 | } -------------------------------------------------------------------------------- /src/Console/SmoothCreateCommand.php: -------------------------------------------------------------------------------- 1 | composer = $composer; 52 | 53 | $this->makeDependencies(); 54 | } 55 | 56 | /** 57 | * Execute the console command. 58 | * 59 | * @return void 60 | */ 61 | public function handle() 62 | { 63 | $this->createFile( 64 | $this->schemaDirectory(), 65 | $this->schemaPath(), $this->populateStub()); 66 | 67 | $this->info('Schema Class created'); 68 | 69 | $this->repository->log( 70 | $this->getSchemaName($this->schemaPath()), 71 | $this->fetchSerializableData($this->schemaPath()) 72 | ); 73 | 74 | $this->info('Dumping Autoload, Please wait...!'); 75 | 76 | $this->composer->dumpAutoloads(); 77 | 78 | $this->info('Process Completed...!'); 79 | } 80 | 81 | /** 82 | * Get Smooth Serializer Path. 83 | * 84 | * @return string 85 | */ 86 | protected function serializerPath() 87 | { 88 | return $this->serializerDirectory() . 89 | $this->getClassName() . 90 | Constants::SMOOTH_SCHEMA_FILE . ".json"; 91 | } 92 | 93 | /** 94 | * Get Smooth Schema Path. 95 | * 96 | * @return string 97 | */ 98 | protected function schemaPath() 99 | { 100 | return $this->schemaDirectory() . 101 | $this->getClassName() . 102 | Constants::SMOOTH_SCHEMA_FILE . ".php"; 103 | } 104 | 105 | /** 106 | * Gets Smooth Schema Class Name 107 | */ 108 | protected function getClassName() 109 | { 110 | return Str::studly($this->argument('table')); 111 | } 112 | 113 | /** 114 | * Replaces Stub holders with the appropriate values 115 | * @return string 116 | */ 117 | protected function populateStub() 118 | { 119 | $stub = $this->files->get(__DIR__ . "/stubs/Schema.stub"); 120 | 121 | $stub = str_replace( 122 | "{{SMOOTH_SCHEMA_CLASS}}", $this->getClassName() . Constants::SMOOTH_SCHEMA_FILE, $stub); 123 | 124 | $stub = str_replace( 125 | "{{TABLE_NAME}}", $this->argument('table'), $stub); 126 | 127 | return $stub; 128 | } 129 | 130 | } -------------------------------------------------------------------------------- /src/Console/stubs/Schema.stub: -------------------------------------------------------------------------------- 1 | Constants::SCHEMA_CREATE_ACTION, 65 | Constants::SCHEMA_UPDATE_ACTION => [ 66 | Constants::FOREIGN_DROP_UP_ACTION, Constants::COLUMN_ADD_UP_ACTION, 67 | Constants::COLUMN_RENAME_UP_ACTION, 68 | Constants::DEF_CHANGE_UP_ACTION, Constants::FOREIGN_ADD_UP_ACTION, 69 | Constants::DROP_MORPH_UP_ACTION, Constants::COLUMN_DROP_UP_ACTION, 70 | Constants::FOREIGN_ADD_DOWN_ACTION, Constants::COLUMN_ADD_DOWN_ACTION, 71 | Constants::DEF_CHANGE_DOWN_ACTION, Constants::FOREIGN_DROP_DOWN_ACTION, 72 | Constants::DROP_MORPH_DOWN_ACTION, Constants::COLUMN_DROP_DOWN_ACTION, 73 | Constants::COLUMN_RENAME_DOWN_ACTION, 74 | ] 75 | ]; 76 | 77 | /** 78 | * Schema Writer 79 | * @var SchemaWriter 80 | */ 81 | protected $writer; 82 | 83 | /** 84 | * Construct 85 | * @param SchemaWriter $writer 86 | */ 87 | public function __construct(SchemaWriter $writer) 88 | { 89 | $this->writer = $writer; 90 | 91 | $this->reader = $this->writer->schema->reader(); 92 | } 93 | 94 | /** 95 | * Schema Composer Factory 96 | * @param SchemaWriter 97 | */ 98 | public static function make(SchemaWriter $writer) 99 | { 100 | $self = new self($writer); 101 | 102 | return $self->init(); 103 | } 104 | 105 | /** 106 | * Handle Schema Composition 107 | * @return void 108 | */ 109 | public function init() 110 | { 111 | $composeMethod = $this->composeMethods[$this->writer->action()]; 112 | 113 | if (is_string($composeMethod)) { 114 | $this->$composeMethod(); 115 | } else { 116 | foreach ($composeMethod as $callable) { 117 | $this->$callable(); 118 | } 119 | } 120 | return $this->finalize(); 121 | } 122 | 123 | /** 124 | * Finish Composition 125 | * @return string 126 | */ 127 | protected function finalize() 128 | { 129 | $lines = array_merge_recursive($this->uplines, $this->foreignLines); 130 | 131 | $downlines = $this->downlines ?? []; 132 | 133 | if ($this->writer->action() == Constants::SCHEMA_UPDATE_ACTION) { 134 | $lines = $this->composeWithBuilder($lines); 135 | $downlines = $this->composeWithBuilder($downlines); 136 | $lines = implode("\n\t\t", $lines); 137 | $downlines = implode("\n\t\t", $downlines); 138 | } else { 139 | $lines = implode("\n\t\t\t", $lines); 140 | $downlines = ""; 141 | } 142 | 143 | return $this->populateStub($this->writer->action(), 144 | $this->writer->migrationClass(), $this->writer->schema->table(), 145 | $lines, $downlines); 146 | } 147 | 148 | /** 149 | * Compose with Schema Builder 150 | * @return array 151 | */ 152 | protected function composeWithBuilder(array $lines) 153 | { 154 | $stub = file_get_contents(__DIR__ . "/stubs/schema_table.stub"); 155 | 156 | return \Illuminate\Support\Collection::make($lines)->map(function($line) use ($stub){ 157 | $stub = str_replace("DummyTable", $this->writer->schema->table(), $stub); 158 | return str_replace("Writer", $line, $stub); 159 | })->all(); 160 | } 161 | 162 | /** 163 | * Replaces Stub holders with the appropriate values 164 | * @param string $action 165 | * @param string $className 166 | * @param string $table 167 | * @param string $upwriter 168 | * @return string 169 | */ 170 | protected function populateStub($action, $className, $table, $upwriter, $downwriter) 171 | { 172 | $stub = file_get_contents(__DIR__ . "/stubs/$action.stub"); 173 | 174 | $stub = str_replace( 175 | "DummyClass", $className, $stub); 176 | 177 | switch ($action) { 178 | case Constants::SCHEMA_CREATE_ACTION: 179 | $stub = str_replace( 180 | "DummyTable", $table, $stub); 181 | $stub = str_replace("UpWriter", $upwriter, $stub); 182 | break; 183 | case Constants::SCHEMA_UPDATE_ACTION: 184 | $stub = str_replace("UpWriter", $upwriter, $stub); 185 | $stub = str_replace("DownWriter", $downwriter, $stub); 186 | break; 187 | } 188 | 189 | return $stub; 190 | } 191 | 192 | /** 193 | * Compose a create table migration 194 | */ 195 | protected function create() 196 | { 197 | foreach ($this->writer->schema->schemas() as $column => $schemas) { 198 | $this->compose($column, $schemas); 199 | $this->composeForeign($column); 200 | } 201 | } 202 | 203 | /** 204 | * Handle Column Definition changes for Up Method 205 | * @return void 206 | */ 207 | protected function defChangeUp() 208 | { 209 | foreach ($this->reader->defChanges() as $previous => $column) { 210 | $this->compose( 211 | $column, $this->reader->currentLoad()[$column], 212 | "uplines", false, $this->afterWrite()); 213 | } 214 | } 215 | 216 | /** 217 | * Handle Column Definition changes for Down Method 218 | * @return void 219 | */ 220 | protected function defChangeDown() 221 | { 222 | foreach ($this->reader->defChanges() as $previous => $column) { 223 | $this->compose( 224 | $column, $this->reader->previousLoad()[$previous], 225 | "downlines", false, $this->afterWrite()); 226 | } 227 | } 228 | 229 | /** 230 | * Get Callback for action to perform after writing 231 | * @return Closure 232 | */ 233 | protected function afterWrite() 234 | { 235 | return function ($line, $lineType) { 236 | $writer = $this->endWrite($line . $this->doOther("change")); 237 | $this->$lineType[] = $writer; 238 | }; 239 | } 240 | 241 | /** 242 | * Handle Column Drops For Up Method 243 | * @return void 244 | */ 245 | protected function columnDropUp() 246 | { 247 | foreach ($this->reader->columnDrops() as $column) { 248 | $dropSchema = $this->columnDropSchema($column); 249 | $this->compose( 250 | key($dropSchema), $dropSchema); 251 | } 252 | } 253 | 254 | /** 255 | * Handle Column Drops for down method 256 | * @return void 257 | */ 258 | protected function columnDropDown() 259 | { 260 | foreach ($this->reader->columnDrops() as $column) { 261 | $this->compose( 262 | $column, $this->reader->previousLoad()[$column], 263 | "downlines"); 264 | $this->composeForeign($column, "downlines"); 265 | } 266 | } 267 | 268 | /** 269 | * Handle Column Additions for up method 270 | * @return void 271 | */ 272 | protected function columnAddUp() 273 | { 274 | foreach ($this->reader->columnAdds() as $column) { 275 | $this->compose( 276 | $column, $this->reader->currentLoad()[$column]); 277 | } 278 | } 279 | 280 | /** 281 | * Handle Column Additions for down method 282 | * @return void 283 | */ 284 | protected function columnAddDown() 285 | { 286 | foreach ($this->reader->columnAdds() as $column) { 287 | $dropSchema = $this->columnDropSchema($column); 288 | $this->compose( 289 | key($dropSchema), $dropSchema, 290 | "downlines"); 291 | } 292 | } 293 | 294 | /** 295 | * Handle Column Rename Action for Up Method 296 | * @return void 297 | */ 298 | protected function columnRenameUp() 299 | { 300 | foreach ($this->reader->columnRenames() as $previous => $current) { 301 | $this->compose( 302 | $previous, $this->columnRenameSchema($current)); 303 | } 304 | } 305 | 306 | /** 307 | * Handle Column Rename Action for Down Method 308 | * @return void 309 | */ 310 | protected function columnRenameDown() 311 | { 312 | foreach ($this->reader->columnRenames() as $previous => $current) { 313 | $this->compose( 314 | $current, $this->columnRenameSchema($previous), "downlines"); 315 | } 316 | } 317 | 318 | /** 319 | * Handle Foreign key Additions For Up Method 320 | * @return void 321 | */ 322 | protected function addForeignUp() 323 | { 324 | foreach ($this->reader->addForeigns() as $column) { 325 | $this->schemaArray($this->reader->currentLoad()[$column], $column); 326 | $this->composeForeign($column); 327 | } 328 | 329 | } 330 | 331 | /** 332 | * Handle Foreign key Additions for down method 333 | * @return void 334 | */ 335 | protected function addForeignDown() 336 | { 337 | foreach ($this->reader->addForeigns() as $column) { 338 | $foreignDropSchema = $this->foreignDropSchema($column); 339 | $this->compose(key($foreignDropSchema), $foreignDropSchema, "downlines"); 340 | } 341 | 342 | } 343 | 344 | /** 345 | * Handle Drop of Foreign keys for up method 346 | * @return void 347 | */ 348 | protected function dropForeignUp() 349 | { 350 | foreach ($this->reader->dropForeigns() as $column) { 351 | $foreignDropSchema = $this->foreignDropSchema($column); 352 | $this->compose(key($foreignDropSchema), $foreignDropSchema); 353 | } 354 | } 355 | 356 | /** 357 | * Handle Drop of Foreign keys for down method 358 | * @return void 359 | */ 360 | protected function dropForeignDown() 361 | { 362 | foreach ($this->reader->dropForeigns() as $column) { 363 | $this->schemaArray($this->reader->previousLoad()[$column], $column); 364 | $this->composeForeign($column, "downlines"); 365 | } 366 | } 367 | 368 | /** 369 | * Handle Drop of morphs for up method 370 | * @return void 371 | */ 372 | protected function dropMorphUp() 373 | { 374 | foreach ($this->reader->dropMorphs() as $column) { 375 | $dropMorphSchema = $this->dropMorphSchema($column); 376 | $this->compose(key($dropMorphSchema), $dropMorphSchema); 377 | } 378 | } 379 | 380 | /** 381 | * Handle Drop of morphs for down method 382 | * @return void 383 | */ 384 | protected function dropMorphDown() 385 | { 386 | foreach ($this->reader->dropMorphs() as $column) { 387 | $this->compose($column, $this->reader->previousLoad()[$column], "downlines"); 388 | } 389 | } 390 | 391 | /** 392 | * Handle foreign schemas 393 | * @param string $column 394 | * @return void 395 | */ 396 | protected function composeForeign(string $column, $holderlines = "foreignLines") 397 | { 398 | if (! empty($this->preparedForeign)) { 399 | $this->compose($column, $this->preparedForeign, $holderlines); 400 | $this->preparedForeign = []; 401 | } 402 | } 403 | 404 | /** 405 | * Handle downlines of foreign schemas 406 | * @param string $column 407 | * @return void 408 | */ 409 | protected function composeDownForeign(string $column) 410 | { 411 | if (! empty($this->preparedForeign)) { 412 | $this->compose($column, $this->preparedForeign, "foreignLines"); 413 | $this->preparedForeign = []; 414 | } 415 | } 416 | 417 | /** 418 | * Compose up migration method 419 | */ 420 | protected function compose($column, $schema, 421 | $linetype = "uplines", $endWrite = true, Closure $afterWrite = null) 422 | { 423 | $schema = is_array($schema) ? $schema : $this->schemaArray($schema, $column); 424 | $writer = "\$table->"; 425 | foreach ($schema as $method => $options) { 426 | $writer = $this->writeLine($method, $options, $column, $writer); 427 | } 428 | 429 | !$endWrite ?: $writer = $this->endWrite($writer); 430 | 431 | return $afterWrite ? $afterWrite($writer, $linetype) : 432 | $this->$linetype[] = $writer; 433 | } 434 | 435 | /** 436 | * Write a migration method line 437 | * @return string 438 | */ 439 | protected function writeLine(string $method, array $options, 440 | string $column, string $writer = "\$table->") { 441 | if (! $this->doneFirst($writer)) { 442 | return $this->doFirst( 443 | $writer, $method, $column, $options); 444 | } 445 | return $writer . $this->doOther($method, $options); 446 | } 447 | 448 | /** 449 | * Check if first method has been chained 450 | * @param string 451 | * @return bool 452 | */ 453 | protected function doneFirst(string $schema) 454 | { 455 | return strlen($schema) > 10; 456 | } 457 | 458 | /** 459 | * Chain first migration method method 460 | * @param string $upwriter 461 | * @param string $method 462 | * @param string $column 463 | * @param array $options 464 | * 465 | * @return string 466 | */ 467 | protected function doFirst(string $upwriter, string $method, string $column, array $options) 468 | { 469 | $this->verifyMethod($method); 470 | $column = $method == $column ? '' : $this->qualify($column); 471 | return $upwriter . $method . "($column" . $this->flatOptions($options, ! empty($column)); 472 | } 473 | 474 | /** 475 | * Chain other migration methods 476 | * @param string $method 477 | * @param array $options 478 | * 479 | * @return string 480 | */ 481 | protected function doOther(string $method, array $options = []) 482 | { 483 | return "->" . $method . "(" . $this->flatOptions($options); 484 | } 485 | 486 | /** 487 | * Qualifies a parameter method 488 | * @param mixed $param 489 | * @return $param 490 | */ 491 | protected function qualify($param) 492 | { 493 | return is_numeric($param) || 494 | $this->isBool($param) || $this->isArray($param) ? $param: "'{$param}'"; 495 | } 496 | 497 | /** 498 | * Checks if a string literal is boolean 499 | * @param mixed $param 500 | * @return bool 501 | */ 502 | protected function isBool($param) 503 | { 504 | return strtolower($param) == "true" || strtolower($param) == "false"; 505 | } 506 | 507 | /** 508 | * Checks if a string literal is an array 509 | * @param mixed $param 510 | * @return bool 511 | */ 512 | protected function isArray($param) 513 | { 514 | return strpos($param, "["); 515 | } 516 | 517 | /** 518 | * Compose options into migration method 519 | * @param array $options 520 | * @param bool $addComma 521 | * @return string 522 | */ 523 | protected function flatOptions(array $options, $addComma = false) 524 | { 525 | if (empty($options)) return ")"; 526 | 527 | $comma = $addComma ? ", " : ""; 528 | 529 | $qoptions = array_map(function($option) { 530 | return $this->qualify($option); 531 | }, $options); 532 | 533 | return $comma . implode(",", $qoptions) . ")"; 534 | } 535 | 536 | /** 537 | * End a writen line 538 | * @param string $writer 539 | * @return string $writer 540 | */ 541 | protected function endWrite(string $writer) 542 | { 543 | return $writer . ";"; 544 | } 545 | 546 | /** 547 | * Compose Schema To Array 548 | * @param string $schema 549 | * @param string $column 550 | * @return array $arrayed 551 | */ 552 | protected function schemaArray(string $schema, string $column) 553 | { 554 | $arrayed = $this->arrayed($schema); 555 | 556 | if ($this->hasForeign($schema) && 557 | !$this->hasReference($schema)) { 558 | $this->addReference($arrayed); 559 | } 560 | return $this->keySchema($arrayed, $column); 561 | 562 | } 563 | 564 | /** 565 | * Checks if a stringed schema has foreign 566 | * @param string $schema 567 | * @return bool 568 | */ 569 | protected function hasForeign(string $schema) 570 | { 571 | return strpos($schema, "on:"); 572 | } 573 | 574 | /** 575 | * Checks if a stringed schema contains reference 576 | * @param string $schema 577 | * @return bool 578 | */ 579 | protected function hasReference(string $schema) 580 | { 581 | return strpos($schema, "references:"); 582 | } 583 | 584 | /** 585 | * Checks if a stringed Schema has options 586 | * @param string $schema 587 | * @return bool 588 | */ 589 | protected function hasOption(string $schema) 590 | { 591 | return strpos($schema, ":"); 592 | } 593 | 594 | /** 595 | * Break a stringed schema into an arrayed schema 596 | * @param string $schema 597 | * @return array $arrayedSchema 598 | */ 599 | protected function arrayed(string $schema) 600 | { 601 | return explode("|", $schema); 602 | } 603 | 604 | /** 605 | * Add reference to foreign 606 | * @param array $arrayed 607 | * @return void 608 | */ 609 | protected function addReference(array $arrayed) 610 | { 611 | array_push($arrayed, "references:id"); 612 | } 613 | 614 | /** 615 | * Verify Blueprint Method 616 | * @param string $method; 617 | */ 618 | protected function verifyMethod(string $method) 619 | { 620 | if (! method_exists( 621 | \Illuminate\Database\Schema\Blueprint::class, $method)) { 622 | throw new Exception("Method: {$method} not found"); 623 | } 624 | } 625 | 626 | /** 627 | * Key each schema methods to options 628 | * @param array $arrayed 629 | * @param string $column 630 | * @return array $keyedArray 631 | */ 632 | protected function keySchema(array $arrayed, string $column) 633 | { 634 | foreach ($arrayed as $stringed) { 635 | $methodOptions = $this->getOptions($stringed); 636 | $keyedArray[$methodOptions[0]] = 637 | $methodOptions[1]; 638 | } 639 | return $this->keyForeignSchema($keyedArray, $column); 640 | } 641 | 642 | /** 643 | * Get Method and Options from stringed schema 644 | * @param string $stringed 645 | * @return array $methodOptions 646 | */ 647 | protected function getOptions(string $stringed) 648 | { 649 | if ($index = $this->hasOption($stringed)) { 650 | $options = explode(",", trim(\substr($stringed, $index + 1))); 651 | $method = trim(\substr($stringed, 0, $index)); 652 | return [$method, $options]; 653 | } 654 | return [trim($stringed), []]; 655 | } 656 | 657 | /** 658 | * Key schema that has foreign key to foreign methods 659 | * @param array $arrayed 660 | * @param string $column 661 | * @return array $foreignKeyedArray 662 | */ 663 | protected function keyForeignSchema(array $arrayed, string $column) 664 | { 665 | if (array_key_exists("on", $arrayed)) { 666 | $this->prepareForeignKey($arrayed); 667 | } 668 | return $arrayed; 669 | } 670 | 671 | /** 672 | * Prepare foreign keys to be writen to line 673 | * @param array $arrayed 674 | * @return void 675 | */ 676 | protected function prepareForeignKey(array &$arrayed) 677 | { 678 | foreach (Constants::FOREIGN_VALUES as $val) { 679 | $foreignArrayed[$val] = $arrayed[$val] ?? []; 680 | if (isset($arrayed[$val])) unset($arrayed[$val]); 681 | } 682 | ! empty($foreignArrayed["references"]) ?: 683 | $foreignArrayed["references"] = ["id"]; 684 | if (isset($arrayed["onDelete"])) { 685 | $foreignArrayed["onDelete"] = $arrayed["onDelete"]; 686 | unset($arrayed["onDelete"]); 687 | } 688 | $arrayed["unsigned"] = []; 689 | $this->preparedForeign = $foreignArrayed ?? []; 690 | } 691 | 692 | /** 693 | * Checks if a column schema definition has morphs 694 | * @param string $column 695 | */ 696 | protected function hasMorphs(string $column) 697 | { 698 | $schema = $this->reader->currentLoad()[$column] ?? ""; 699 | $arrayedSchema = $this->schemaArray($schema, $column); 700 | return array_key_exists("morphs", $arrayedSchema) 701 | || array_key_exists("nullableMorphs", $arrayedSchema); 702 | } 703 | 704 | /** 705 | * Get Column Drop Schema 706 | * @return array 707 | */ 708 | protected function columnDropSchema($column) 709 | { 710 | if ($this->hasMorphs($column)) { 711 | return $this->dropMorphSchema($column); 712 | } 713 | if ($column == Constants::SOFT_DELETE) { 714 | return ["dropSoftDeletes" => []]; 715 | } elseif ($column == Constants::TIMESTAMP) { 716 | return ["dropTimestamps" => []]; 717 | } elseif ($column == Constants::REMEMBER_TOKEN) { 718 | return ["dropRememberToken" => []]; 719 | } 720 | return ["dropColumn" => [$column]]; 721 | } 722 | 723 | /** 724 | * Get Foreign Drop Schema 725 | * @param string $column 726 | * @return array 727 | */ 728 | protected function foreignDropSchema($column) 729 | { 730 | $drop_syntax = $this->writer->schema->table() . "_" . $column . "_foreign"; 731 | return ["dropForeign" => [$drop_syntax]]; 732 | } 733 | 734 | /** 735 | * Get Primary Key Drop Schema 736 | * @param string $column 737 | * @return array 738 | */ 739 | protected function primaryDropSchema($column) 740 | { 741 | $drop_syntax = $this->writer->schema->table() . "_" . $column . "_foreign"; 742 | return ["dropForeign" => [$drop_syntax]]; 743 | } 744 | 745 | /** 746 | * Get Drop Morph Schema 747 | * @param string $column 748 | * @return array 749 | */ 750 | protected function dropMorphSchema($column) 751 | { 752 | return ["dropMorphs" => [$column]]; 753 | } 754 | 755 | /** 756 | * Column Rename Schema 757 | * @param string $column 758 | * @return array 759 | */ 760 | protected function columnRenameSchema($column) 761 | { 762 | return ["renameColumn" => [$column]]; 763 | } 764 | } -------------------------------------------------------------------------------- /src/Helpers/SchemaReader.php: -------------------------------------------------------------------------------- 1 | previousTable = $previousSchemaLoad["table"]; 144 | $this->currentTable = $currentSchemaLoad["table"]; 145 | $this->previousLoad = $previousSchemaLoad["schemas"]; 146 | $this->currentLoad = $currentSchemaLoad["schemas"]; 147 | $this->previousSchemas = array_values($previousSchemaLoad["schemas"]); 148 | $this->currentSchemas = array_values($currentSchemaLoad["schemas"]); 149 | $this->previousColumns = array_keys($previousSchemaLoad["schemas"]); 150 | $this->currentColumns = array_keys($currentSchemaLoad["schemas"]); 151 | $this->read(); 152 | } 153 | 154 | /** 155 | * Read schema and checks for changes 156 | * @return void 157 | */ 158 | protected function read() 159 | { 160 | if ($this->previousTable != $this->currentTable) { 161 | $this->pushChange(Constants::TABLE_RENAME_UP_ACTION, [ 162 | $this->previousTable, 163 | $this->currentTable 164 | ]); 165 | } 166 | if (($previous = count($this->previousColumns)) != 167 | ($current = count($this->currentColumns))) { 168 | return $this->readByColumnDifference($previous, $current); 169 | } 170 | $this->readByColumn(); 171 | } 172 | 173 | /** 174 | * Read Schema By Columns 175 | * @return void 176 | */ 177 | protected function readByColumn($index = 0) 178 | { 179 | if ($index < count($this->previousColumns)) { 180 | if ($this->previousColumns[$index] != $this->currentColumns[$index]) { 181 | $this->pushChanges(Constants::COLUMN_RENAME_UP_ACTION, [ 182 | $this->previousColumns[$index], 183 | $this->currentColumns[$index] 184 | ]); 185 | } 186 | return $this->readByColumn($index = $index + 1); 187 | } 188 | return $this->readBySchema(); 189 | } 190 | 191 | /** 192 | * Read Schema by Schema 193 | * @return void 194 | */ 195 | protected function readBySchema($index = 0) 196 | { 197 | if ($index < count($this->previousSchemas)) { 198 | $previousSchemaArray = $this->schemaToArray($this->previousSchemas[$index]); 199 | $currentSchemaArray = $this->schemaToArray($this->currentSchemas[$index]); 200 | if ($this->schemaisDifferent($previousSchemaArray, $currentSchemaArray)) { 201 | $this->pushChanges(Constants::DEF_CHANGE_UP_ACTION, [ 202 | $index, $previousSchemaArray, $currentSchemaArray 203 | ]); 204 | } 205 | return $this->readBySchema($index = $index + 1); 206 | 207 | } 208 | } 209 | 210 | /** 211 | * Checks if schema is different 212 | * @param array $previous 213 | * @param array $current 214 | * @return bool 215 | */ 216 | protected function schemaIsDifferent($previous, $current) 217 | { 218 | return ! ($previous == $current); 219 | } 220 | 221 | /** 222 | * Read By Column when previous and current column 223 | * count do not match 224 | * @param int $previousCount 225 | * @param int $currentCount 226 | * @return void 227 | */ 228 | protected function readByColumnDifference($previousCount, $currentCount) 229 | { 230 | $shouldDropColumn = function ($previous, $current) { 231 | return $previous > $current; 232 | }; 233 | 234 | if ($shouldDropColumn($previousCount, $currentCount)) { 235 | $this->pushChanges(Constants::COLUMN_DROP_UP_ACTION, array_diff( 236 | $this->previousColumns, $this->currentColumns)); 237 | } else { 238 | $this->pushChanges(Constants::COLUMN_ADD_UP_ACTION, array_diff( 239 | $this->currentColumns, $this->previousColumns 240 | )); 241 | } 242 | 243 | return $this->readBySchemaDifference(); 244 | 245 | } 246 | 247 | /** 248 | * Read Schema of Columns when count do not match 249 | * @return void 250 | */ 251 | protected function readBySchemaDifference() 252 | { 253 | $unchangedColumns = array_intersect( 254 | $this->previousColumns, $this->currentColumns); 255 | 256 | foreach ($unchangedColumns as $column) { 257 | $previousSchemaArray = $this->schemaToArray( 258 | $this->previousLoad[$column]); 259 | $currentSchemaArray = $this->schemaToArray( 260 | $this->currentLoad[$column]); 261 | $index = array_search($column, $this->currentColumns); 262 | if ($this->schemaisDifferent($previousSchemaArray, $currentSchemaArray)) { 263 | $this->pushChanges(Constants::DEF_CHANGE_UP_ACTION, [ 264 | $index, $previousSchemaArray, $currentSchemaArray 265 | ]); 266 | } 267 | } 268 | } 269 | 270 | /** 271 | * Get Array Representation of stringed Schema 272 | * @param string $schema 273 | * @return array 274 | */ 275 | protected function schemaToArray($schema) 276 | { 277 | $hasForeign = function ($schema) { 278 | return strpos($schema, "on:"); 279 | }; 280 | 281 | $hasReference = function ($schema) { 282 | return strpos($schema, "references:"); 283 | }; 284 | 285 | $hasOptions = function ($schema) { 286 | return strpos($schema, ":"); 287 | }; 288 | 289 | $getOptions = function ($schema) use ($hasOptions){ 290 | if ($index = $hasOptions($schema)) { 291 | 292 | $options = explode(",", trim(\substr($schema, $index + 1))); 293 | 294 | $method = trim(\substr($schema, 0, $index)); 295 | 296 | array_push($options, $method); 297 | 298 | return $options; 299 | } 300 | return [trim($schema)]; 301 | }; 302 | 303 | $arrayedSchema = explode("|" , $schema); 304 | 305 | $finalSchema = []; 306 | 307 | if ($hasForeign($schema)) { 308 | if (! $hasReference($schema)) { 309 | array_push($arrayedSchema, "references:id"); 310 | } 311 | } 312 | 313 | foreach ($arrayedSchema as $arraySchema) { 314 | $finalSchema = array_merge_recursive($finalSchema, $getOptions($arraySchema)); 315 | } 316 | 317 | return $finalSchema; 318 | } 319 | 320 | /** 321 | * Reads Change value 322 | * @return bool 323 | */ 324 | public function hasChanged() 325 | { 326 | return ! empty($this->changelogs); 327 | } 328 | 329 | /** 330 | * Pushes Change to Change Holders 331 | * @param string $action 332 | * @param array $affected 333 | */ 334 | protected function pushChanges($action, $affected) 335 | { 336 | $method = "push" . Str::studly($action) . "Action"; 337 | 338 | if (method_exists($this, $method)) { 339 | return $this->$method($affected); 340 | } 341 | throw new \Exception("Schema Read Method {$action} not supported"); 342 | } 343 | 344 | /** 345 | * Push Table Rename Action 346 | * @param array $affected 347 | */ 348 | protected function pushTableRenameUpAction($affected = []) 349 | { 350 | if (empty($affected)) return; 351 | 352 | array_push($this->tableRenames, $affected[1]); 353 | 354 | $changelog = "Table renamed from ". $affected[0] . " to " . $affected[1]; 355 | 356 | array_push($this->changelogs, $changelog); 357 | } 358 | 359 | /** 360 | * Push Column Rename Action 361 | * @param array $affected 362 | */ 363 | protected function pushColumnRenameUpAction($affected = []) 364 | { 365 | if (empty($affected)) return; 366 | 367 | $this->columnRenames[$affected[0]] = $affected[1]; 368 | 369 | $changelog = "Column renamed from ". $affected[0] . " to " . $affected[1]; 370 | 371 | array_push($this->changelogs, $changelog); 372 | } 373 | 374 | /** 375 | * Push Column Defination Changes 376 | * @param array $index 377 | */ 378 | protected function pushDefChangeUpAction($affected = []) 379 | { 380 | if (empty($affected)) return; 381 | 382 | $shouldDrop = function ($previous, $current, $value = "on") { 383 | if (in_array($value, $previous) && !in_array($value, $current)) { 384 | return true; 385 | } 386 | return in_array("on", $previous) && in_array("on", $current); 387 | }; 388 | 389 | $shouldAddForeign = function ($previous, $current) { 390 | if (!in_array("on", $previous) && in_array("on", $current)) { 391 | return true; 392 | } 393 | return in_array("on", $previous) && in_array("on", $current); 394 | }; 395 | 396 | $column = $this->currentColumns[$affected[0]]; 397 | $previousColumn = $this->previousColumns[$affected[0]]; 398 | 399 | if ($shouldDrop($affected[1], $affected[2])) { 400 | $this->pushChanges(Constants::FOREIGN_DROP_UP_ACTION, [ 401 | $column 402 | ]); 403 | } 404 | 405 | if ($shouldDrop($affected[1], $affected[2], "primary")) { 406 | $this->pushChanges(Constants::DROP_PRIMARY_UP_ACTION,[ 407 | $column]); 408 | } 409 | 410 | if ($shouldDrop($affected[1], $affected[2], "unique")) { 411 | $this->pushChanges(Constants::DROP_UNIQUE_UP_ACTION, [ 412 | $column]); 413 | } 414 | 415 | if ($shouldDrop($affected[1], $affected[2], "index")) { 416 | $this->pushChanges(Constants::DROP_INDEX_UP_ACTION,[ 417 | $column]); 418 | } 419 | 420 | if ($shouldAddForeign($affected[1], $affected[2])) { 421 | $this->pushChanges(Constants::FOREIGN_ADD_UP_ACTION, [ 422 | $column]); 423 | } 424 | 425 | $this->defChanges[$previousColumn] = $column; 426 | 427 | $changelog = "Column '{$column}' schema altered"; 428 | 429 | array_push($this->changelogs, $changelog); 430 | } 431 | 432 | /** 433 | * Push Primary Drop Action 434 | * @param array $affected 435 | */ 436 | protected function pushDropPrimaryUpAction($affected = []) 437 | { 438 | if (empty($affected)) return; 439 | 440 | array_push($this->dropPrimaries, $affected[0]); 441 | 442 | $changelog = "Primary Key Dropped on ". $affected[0]; 443 | 444 | array_push($this->changelogs, $changelog); 445 | } 446 | 447 | /** 448 | * Push Unique Drop Action 449 | * @param array $affected 450 | */ 451 | protected function pushDropUniqueUpAction($affected = []) 452 | { 453 | if (empty($affected)) return; 454 | 455 | array_push($this->dropUniques, $affected[0]); 456 | 457 | $changelog = "Unique Key Dropped on ". $affected[0]; 458 | 459 | array_push($this->changelogs, $changelog); 460 | } 461 | 462 | /** 463 | * Push Index Drop Action 464 | * @param array $affected 465 | */ 466 | protected function pushDropIndexUpAction($affected = []) 467 | { 468 | if (empty($affected)) return; 469 | 470 | array_push($this->dropIndices, $affected[0]); 471 | 472 | $changelog = "Index Key Dropped on ". $affected[0]; 473 | 474 | array_push($this->changelogs, $changelog); 475 | } 476 | 477 | /** 478 | * Push Foreign Drop Action 479 | * @param array $affected 480 | */ 481 | protected function pushAddForeignUpAction($affected = []) 482 | { 483 | if (empty($affected)) return; 484 | 485 | array_push($this->addForeigns, $affected[0]); 486 | 487 | $changelog = "Foreign Key added on ". $affected[0]; 488 | 489 | array_push($this->changelogs, $changelog); 490 | } 491 | 492 | /** 493 | * Push Foreign Drop Action 494 | * @param array $affected 495 | */ 496 | protected function pushDropForeignUpAction($affected = []) 497 | { 498 | if (empty($affected)) return; 499 | 500 | array_push($this->dropForeigns, $affected[0]); 501 | 502 | $changelog = "Foreign Key dropped on ". $affected[0]; 503 | 504 | array_push($this->changelogs, $changelog); 505 | } 506 | 507 | /** 508 | * Push polymorphic relationship drop action 509 | * @param array $affected 510 | */ 511 | protected function pushDropMorphUpAction($affected = []) 512 | { 513 | if (empty($affected)) return; 514 | 515 | array_push($this->dropMorphs, $affected[0]); 516 | 517 | $changelog = "Morphable Relationship dropped on ". $affected[0]; 518 | 519 | array_push($this->changelogs, $changelog); 520 | } 521 | 522 | /** 523 | * Push Column Drop Changes 524 | * @param array $affected 525 | */ 526 | protected function pushColumnDropUpAction($affected = []) 527 | { 528 | if (empty($affected)) return; 529 | 530 | $this->shouldPushDropMorph($affected); 531 | 532 | $withoutMorphs = array_filter($affected, function($column){ 533 | return !in_array($column, $this->dropMorphs()); 534 | }); 535 | 536 | $this->columnDrops = array_merge($this->columnDrops, $withoutMorphs); 537 | 538 | $changelog = count($this->columnDrops) . " Column(s) Dropped"; 539 | 540 | array_push($this->changelogs, $changelog); 541 | } 542 | 543 | /** 544 | * Push Column Add Changes 545 | * @param array $affected 546 | */ 547 | protected function pushColumnAddUpAction($affected = []) 548 | { 549 | if (empty($affected)) return; 550 | 551 | $this->shouldPushForeign($affected, "currentLoad"); 552 | 553 | $this->columnAdds = array_merge($this->columnAdds, $affected); 554 | 555 | $changelog = count($this->columnAdds) . " Column(s) Added"; 556 | 557 | array_push($this->changelogs, $changelog); 558 | } 559 | 560 | /** 561 | * Checks whether to push to foreign keys 562 | * @param array 563 | * @param bool 564 | */ 565 | protected function shouldPushForeign(array $affected, $load, $addForeign = true) 566 | { 567 | foreach ($affected as $column) { 568 | $arrayed = $this->schemaToArray( 569 | $this->$load[$column] ); 570 | if (in_array("on", $arrayed) && $addForeign) { 571 | $this->pushChanges( 572 | Constants::FOREIGN_ADD_UP_ACTION, [$column]); 573 | } else if (in_array("on", $arrayed) && !$addForeign) { 574 | $this->pushChanges( 575 | Constants::FOREIGN_DROP_UP_ACTION, [$column]); 576 | } 577 | } 578 | } 579 | 580 | /** 581 | * Checks whether to push to drop morphs 582 | * @param array 583 | * @return void 584 | */ 585 | protected function shouldPushDropMorph(array $affected) 586 | { 587 | foreach ($affected as $column) { 588 | $arrayed = $this->schemaToArray( 589 | $this->previousLoad[$column] ); 590 | if (in_array("morphs", $arrayed) || in_array("nullableMorphs", $arrayed)) { 591 | $this->pushChanges( 592 | Constants::DROP_MORPH_UP_ACTION, [$column]); 593 | } 594 | } 595 | } 596 | 597 | /** 598 | * Checks whether foreign key methods has changed 599 | */ 600 | protected function foreignKeySchemaHasChanged($previous, $current) 601 | { 602 | if (in_array("on", $previous) && in_array("on", $current)) { 603 | if (array_diff($previous, $current) || array_diff($current, $previous)) { 604 | return true; 605 | } 606 | } 607 | return false; 608 | } 609 | 610 | 611 | /** 612 | * Return Change Logs 613 | * @return array 614 | */ 615 | public function changelogs() 616 | { 617 | return $this->changelogs; 618 | } 619 | 620 | /** 621 | * Table Rename Changes 622 | * @return array 623 | */ 624 | public function tableRenames() 625 | { 626 | return $this->tableRenames; 627 | } 628 | 629 | /** 630 | * Column Rename Changes 631 | * @return array 632 | */ 633 | public function columnRenames() 634 | { 635 | return $this->columnRenames; 636 | } 637 | 638 | /** 639 | * Column Drop Changes 640 | * @return array 641 | */ 642 | public function columnDrops() 643 | { 644 | return $this->columnDrops; 645 | } 646 | 647 | /** 648 | * Column Add Changes 649 | * @return array 650 | */ 651 | public function columnAdds() 652 | { 653 | return $this->columnAdds; 654 | } 655 | 656 | /** 657 | * Column Defination Changes 658 | * @return array 659 | */ 660 | public function defChanges() 661 | { 662 | return $this->defChanges; 663 | } 664 | 665 | /** 666 | * Drop Foreign Key Changes 667 | * @return array 668 | */ 669 | public function dropForeigns() 670 | { 671 | return $this->dropForeigns; 672 | } 673 | 674 | /** 675 | * Previoustable 676 | * @return array 677 | */ 678 | public function previousTable() 679 | { 680 | return $this->previousTable; 681 | } 682 | 683 | /** 684 | * Currenttable 685 | * @return array 686 | */ 687 | public function currentTable() 688 | { 689 | return $this->currentTable; 690 | } 691 | 692 | /** 693 | * Previousload 694 | * @return array 695 | */ 696 | public function previousLoad() 697 | { 698 | return $this->previousLoad; 699 | } 700 | 701 | /** 702 | * Currentload 703 | * @return array 704 | */ 705 | public function currentLoad() 706 | { 707 | return $this->currentLoad; 708 | } 709 | 710 | /** 711 | * Add Foreign Key Changes 712 | * @return array 713 | */ 714 | public function addForeigns() 715 | { 716 | return $this->addForeigns; 717 | } 718 | 719 | /** 720 | * Drop Primary Key Changes 721 | * @return array 722 | */ 723 | public function dropPrimaries() 724 | { 725 | return $this->dropPrimaries; 726 | } 727 | 728 | /** 729 | * Drop Unique Key Changes 730 | * @return array 731 | */ 732 | public function dropUniques() 733 | { 734 | return $this->dropUniques; 735 | } 736 | 737 | /** 738 | * Drop Index Key Changes 739 | * @return array 740 | */ 741 | public function dropIndices() 742 | { 743 | return $this->dropIndices; 744 | } 745 | 746 | /** 747 | * Drop Morph Changes 748 | * @return array 749 | */ 750 | public function dropMorphs() 751 | { 752 | return $this->dropMorphs; 753 | } 754 | 755 | } -------------------------------------------------------------------------------- /src/Helpers/SchemaWriter.php: -------------------------------------------------------------------------------- 1 | schema = $schema; 46 | $this->runCount = $runCount; 47 | $this->setaction(); 48 | $this->makeMigrationClass(); 49 | } 50 | 51 | /** 52 | * Sets action for shema writing 53 | * @return string 54 | */ 55 | protected function setaction() 56 | { 57 | $this->action = IlluminateSchema::hasTable($this->schema->table()) ? 58 | Constants::SCHEMA_UPDATE_ACTION : 59 | Constants::SCHEMA_CREATE_ACTION ; 60 | } 61 | 62 | /** 63 | * Return Current Action 64 | * @return string 65 | */ 66 | public function action() 67 | { 68 | return $this->action; 69 | } 70 | 71 | /** 72 | * Get Migration Path 73 | * @return string 74 | */ 75 | public function migrationPath() 76 | { 77 | return $this->migrationDirectory() . 78 | date('Y_m_d_His') . $this->runCount . "_" . 79 | Str::snake($this->migrationClass()) . 80 | ".php"; 81 | } 82 | 83 | /** 84 | * Get Migration Directory 85 | * @return string 86 | */ 87 | public function migrationDirectory() 88 | { 89 | return database_path("migrations/"); 90 | } 91 | 92 | /** 93 | * Get Migration Class 94 | * @return string 95 | */ 96 | public function migrationClass() 97 | { 98 | return $this->migrationClass; 99 | } 100 | 101 | /** 102 | * Form a migration class 103 | * @param string $class 104 | * @return void 105 | */ 106 | public function makeMigrationClass($class = null) 107 | { 108 | $this->migrationClass = $class ?? Str::studly($this->schema->className() . "MigrationOn" . 109 | Carbon::now()->format("DMYhis")); 110 | } 111 | 112 | /** 113 | * Call write action method 114 | * @return void 115 | * @throws \Exception 116 | */ 117 | public function write() 118 | { 119 | return SchemaComposer::make($this); 120 | } 121 | 122 | } -------------------------------------------------------------------------------- /src/Helpers/stubs/blank.stub: -------------------------------------------------------------------------------- 1 | registerMigrateMakeCommand(); 21 | } 22 | 23 | /** 24 | * Register the command. 25 | * 26 | * @return void 27 | */ 28 | protected function registerMigrateMakeCommand() 29 | { 30 | $this->app->singleton('command.migrate.make', function ($app) { 31 | // Once we have the migration creator registered, we will create the command 32 | // and inject the creator. The creator is responsible for the actual file 33 | // creation of the migrations, and may be extended by these developers. 34 | $creator = $app['migration.creator']; 35 | 36 | $composer = $app['composer']; 37 | 38 | return new MigrateMakeCommand($creator, $composer); 39 | }); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /src/Providers/SmoothMigrationProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(SmoothMigrationRepository::class, function($app) { 20 | $repository = new SmoothMigrationRepository($app["db"], Constants::SMOOTH_TABLE); 21 | $repository->setSource(config("database.default")); 22 | return $repository; 23 | }); 24 | } 25 | 26 | /** 27 | * Bootstrap any application services. 28 | * 29 | * @return void 30 | */ 31 | public function boot() 32 | { 33 | if ($this->app->runningInConsole()) { 34 | $this->commands([ 35 | SmoothCreateCommand::class 36 | ]); 37 | } 38 | 39 | $this->publishes([ 40 | base_path('/vendor/nwogu/smooth-migration/config/smooth.php') => config_path('smooth.php') 41 | ], 'smooth-config'); 42 | 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/Repositories/SmoothMigrationRepository.php: -------------------------------------------------------------------------------- 1 | table = $table; 40 | $this->resolver = $resolver; 41 | } 42 | 43 | /** 44 | * Get the last migration batch. 45 | * @param string $schemaClass 46 | * 47 | * @return object 48 | */ 49 | public function getLast(string $schemaClass) 50 | { 51 | return $this->table()->where('schema_class', $schemaClass) 52 | ->where('batch', $this->getLastBatchNumber($schemaClass))->first(); 53 | } 54 | 55 | /** 56 | * Get the last migration path 57 | * @param string $schemaClass 58 | * 59 | * @return string|bool $migrationPath 60 | */ 61 | public function getLastMigration(string $schemaClass) 62 | { 63 | $lastRun = $this->getLast($schemaClass); 64 | 65 | return $lastRun ? $lastRun->migration_path : false; 66 | } 67 | 68 | /** 69 | * Get previous schema load by batch. 70 | * @param string $schemaClass 71 | * @param int $batch 72 | * 73 | * @return array 74 | */ 75 | public function previousSchemaLoad(string $schemaClass, int $batch) 76 | { 77 | $query = $this->table() 78 | ->where('schema_class', $schemaClass); 79 | while (! $query->where('batch', $batch)->exists() && $batch > 0) { 80 | $batch--; 81 | } 82 | $load = optional($query->where('batch', $batch)->first())->schema_load; 83 | return json_decode($load, true); 84 | } 85 | 86 | /** 87 | * Log that a migration was run. 88 | * 89 | * @param string $schemaClass 90 | * @param string $schemaLoad 91 | * @param string|null $migrationPath 92 | * @param int $batch 93 | * @return void 94 | */ 95 | public function log(string $schemaClass, string $schemaLoad, $migrationPath = null, int $batch = 0) 96 | { 97 | $record = [ 98 | 'schema_class' => $schemaClass, 99 | 'schema_load' => $schemaLoad, 100 | 'migration_path' => $migrationPath, 101 | 'batch' => $batch 102 | ]; 103 | 104 | $this->table()->insert($record); 105 | } 106 | 107 | /** 108 | * Get the next migration batch number. 109 | * @param string $schemaClass 110 | * 111 | * @return int 112 | */ 113 | public function getNextBatchNumber(string $schemaClass) 114 | { 115 | return $this->getLastBatchNumber($schemaClass) + 1; 116 | } 117 | 118 | /** 119 | * Get the last migration batch number. 120 | * @param string $schemaClass 121 | * 122 | * @return int 123 | */ 124 | public function getLastBatchNumber(string $schemaClass) 125 | { 126 | return $this->table()->where("schema_class", $schemaClass) 127 | ->max('batch'); 128 | } 129 | 130 | /** 131 | * Create the migration repository data store. 132 | * 133 | * @return void 134 | */ 135 | public function createRepository() 136 | { 137 | $schema = $this->getConnection()->getSchemaBuilder(); 138 | 139 | $schema->create($this->table, function ($table) { 140 | // The migrations table is responsible for keeping track of which of the 141 | // migrations have actually run for the application. We'll create the 142 | // table to hold the migration file's path as well as the batch ID. 143 | $table->increments('id'); 144 | $table->string('schema_class'); 145 | $table->longText('schema_load'); 146 | $table->string('migration_path')->nullable(); 147 | $table->integer('batch'); 148 | }); 149 | } 150 | 151 | /** 152 | * Determine if the migration repository exists. 153 | * 154 | * @return bool 155 | */ 156 | public function repositoryExists() 157 | { 158 | $schema = $this->getConnection()->getSchemaBuilder(); 159 | 160 | return $schema->hasTable($this->table); 161 | } 162 | 163 | /** 164 | * Get a query builder for the migration table. 165 | * 166 | * @return \Illuminate\Database\Query\Builder 167 | */ 168 | protected function table() 169 | { 170 | return $this->getConnection()->table($this->table)->useWritePdo(); 171 | } 172 | 173 | /** 174 | * Get the connection resolver instance. 175 | * 176 | * @return \Illuminate\Database\ConnectionResolverInterface 177 | */ 178 | public function getConnectionResolver() 179 | { 180 | return $this->resolver; 181 | } 182 | 183 | /** 184 | * Resolve the database connection instance. 185 | * 186 | * @return \Illuminate\Database\Connection 187 | */ 188 | public function getConnection() 189 | { 190 | return $this->resolver->connection($this->connection); 191 | } 192 | 193 | /** 194 | * Set the information source to gather data. 195 | * 196 | * @param string $name 197 | * @return void 198 | */ 199 | public function setSource($name) 200 | { 201 | $this->connection = $name; 202 | } 203 | } -------------------------------------------------------------------------------- /src/Traits/SmoothMigratable.php: -------------------------------------------------------------------------------- 1 | files = app()->make(Filesystem::class); 39 | } 40 | 41 | /** 42 | * Resolve an instance of Smooth Migration Repository 43 | * 44 | * @return \Nwogu\SmoothMigration\Repositories\SmoothMigrationRepository 45 | */ 46 | public function makeRepository() 47 | { 48 | $this->repository = app()->make(SmoothMigrationRepository::class); 49 | $this->prepareDatabase(); 50 | } 51 | 52 | /** 53 | * Initialize neccesary dependency properties 54 | * 55 | * @return void 56 | */ 57 | public function makeDependencies() 58 | { 59 | $this->makeFile(); 60 | $this->makeRepository(); 61 | } 62 | 63 | /** 64 | * Get Smooth Serializer Directiory 65 | * 66 | * @return string 67 | */ 68 | protected function serializerDirectory() 69 | { 70 | return config("smooth.serializer_path", $this->laravel->databasePath() . 71 | DIRECTORY_SEPARATOR . Constants::SMOOTH_SERIALIZER_FOLDER . DIRECTORY_SEPARATOR); 72 | } 73 | 74 | /** 75 | * Get Smooth Schema Directory 76 | * 77 | * @return string 78 | */ 79 | protected function schemaDirectory() 80 | { 81 | return config("smooth.schema_path", $this->laravel->databasePath() . 82 | DIRECTORY_SEPARATOR . Constants::SMOOTH_SCHEMA_FOLDER . DIRECTORY_SEPARATOR); 83 | } 84 | 85 | /** 86 | * Get the name of the Schema. 87 | * 88 | * @param string $path 89 | * @return string 90 | */ 91 | public function getSchemaName($path) 92 | { 93 | return str_replace('.php', '', basename($path)); 94 | } 95 | 96 | /** 97 | * Get class name from path 98 | * @param string $path 99 | * @return string 100 | */ 101 | public function getMigrationClassNameFromPath(string $path) 102 | { 103 | $name = $this->getSchemaName($path); 104 | return Str::studly(implode('_', array_slice(explode('_', $name), 4))); 105 | } 106 | 107 | /** 108 | * Get Instance of a smooth Schema class 109 | * @param string $path 110 | * @param bool $namespaced 111 | * @return Schema 112 | */ 113 | protected function schemaInstance($path) 114 | { 115 | $class = $this->getSchemaName($path); 116 | 117 | $path = $this->getSchemaFullPath($path); 118 | 119 | $this->files->requireOnce($path); 120 | 121 | return new $class; 122 | } 123 | 124 | /** 125 | * Get Schema Full Path 126 | * @param string $path 127 | */ 128 | protected function getSchemaFullPath(string $path) 129 | { 130 | $isFullPath = strpos($path, ".php"); 131 | 132 | return $isFullPath ? $path : $this->schemaDirectory() . $path . ".php"; 133 | } 134 | 135 | /** 136 | * Fetch Serializable Data from Schema Class 137 | * @param $schemaClass 138 | * @return json 139 | */ 140 | protected function fetchSerializableData($schemaClass) 141 | { 142 | if (! $schemaClass instanceof Schema) { 143 | $schemaClass = $this->schemaInstance($schemaClass); 144 | } 145 | 146 | return json_encode( 147 | $schemaClass->currentSchemaLoad()); 148 | } 149 | 150 | /** 151 | * Handle Smooth File Creations 152 | * @param string $directory 153 | * @param string $file 154 | * @param string $data 155 | */ 156 | protected function createFile($directory, $file, $data) 157 | { 158 | if (! $this->files->exists($directory)) { 159 | $this->files->makeDirectory( 160 | $directory); 161 | } 162 | $this->files->put($file, $data); 163 | } 164 | 165 | /** 166 | * Prepare database to persist smooth migration info that has been run 167 | */ 168 | protected function prepareDatabase() 169 | { 170 | if (! $this->repository->repositoryExists()) { 171 | $this->repository->createRepository(); 172 | } 173 | } 174 | 175 | } 176 | --------------------------------------------------------------------------------