├── .gitignore ├── tests ├── bootstrap.php ├── json │ ├── proposed-schema-structure-2.json │ ├── test.json │ └── proposed-schema-structure-1.json └── MakeMigrationJsonTest.php ├── CONTRIBUTING.md ├── .travis.yml ├── src ├── MakeMigrationJsonServiceProvider.php ├── MakeMigrationJson.php └── Commands │ └── MakeMigrationJsonCommand.php ├── composer.json ├── phpunit.xml ├── LICENSE.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /vendor 3 | composer.lock 4 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | registerMigrationJsonGenerator(); 25 | } 26 | 27 | /** 28 | * Register the make:migration:json generator. 29 | * 30 | * @return void 31 | */ 32 | private function registerMigrationJsonGenerator() 33 | { 34 | $this->app->singleton('command.mojopollo.migrate.json', function ($app) { 35 | return $app['Mojopollo\Schema\Commands\MakeMigrationJsonCommand']; 36 | }); 37 | 38 | $this->commands('command.mojopollo.migrate.json'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mojopollo/laravel-json-schema", 3 | "description": "Create all your migrations and models from one JSON schema file. Laravel Database Schema in JSON allows you to define your entire Laravel database schema in one JSON file then generates all the necessary migration files", 4 | "keywords": ["mojopollo", "mojo", "laravel", "json", "schema", "database", "laravel migrations from json"], 5 | "homepage": "http://localhost", 6 | "license" : "MIT", 7 | "authors": [ 8 | { 9 | "name": "Jaime Medina", 10 | "email": "jmedina@qujo.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.4.0", 15 | "illuminate/support": "~5.0", 16 | "illuminate/filesystem": "~5.0", 17 | "illuminate/database": "~5.0", 18 | "laracasts/generators": "dev-master#4e9ce5db9d93475ca27b993a92de3b15090aa139" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "4.7.*" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Mojopollo\\Schema\\": "src/" 26 | } 27 | }, 28 | "minimum-stability": "stable" 29 | } 30 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | 20 | ./src 21 | 22 | ./src/MakeMigrationJsonServiceProvider.php 23 | ./src/Commands/MakeMigrationJsonCommand.php 24 | ./vendor 25 | ./tests 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /tests/json/proposed-schema-structure-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "tables": [ 3 | { 4 | "name": "users", 5 | "fields": [ 6 | { 7 | "name": "email", 8 | "type": "string:nullable:index" 9 | }, 10 | { 11 | "name": "password", 12 | "type": "string:nullable:index" 13 | }, 14 | { 15 | "name": "phone", 16 | "type": "string:nullable" 17 | }, 18 | { 19 | "name": "is_guest", 20 | "type": "boolean:index" 21 | }, 22 | { 23 | "name": "first_name", 24 | "type": "string:nullable" 25 | }, 26 | { 27 | "name": "last_name", 28 | "type": "string:nullable" 29 | }, 30 | { 31 | "name": "address", 32 | "type": "string:nullable" 33 | }, 34 | { 35 | "name": "city", 36 | "type": "string:nullable" 37 | }, 38 | { 39 | "name": "state", 40 | "type": "string:nullable" 41 | }, 42 | { 43 | "name": "zipcode", 44 | "type": "string:nullable" 45 | }, 46 | { 47 | "name": "country", 48 | "type": "string:nullable" 49 | }, 50 | { 51 | "name": "last_ip_address", 52 | "type": "string:nullable" 53 | }, 54 | { 55 | "name": "last_user_agent", 56 | "type": "string:nullable" 57 | }, 58 | { 59 | "name": "last_coordinates", 60 | "type": "string:nullable" 61 | }, 62 | { 63 | "name": "active_at", 64 | "type": "timestamp:nullable:index" 65 | } 66 | ] 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | JSON database schema for Laravel 3 | ======================== 4 | [![Build Status](https://travis-ci.org/mojopollo/laravel-json-schema.svg?branch=master)](https://travis-ci.org/mojopollo/laravel-json-schema) 5 | [![Coverage Status](https://coveralls.io/repos/github/mojopollo/laravel-json-schema/badge.svg?branch=master)](https://coveralls.io/github/mojopollo/laravel-json-schema?branch=master) 6 | [![Latest Stable Version](https://poser.pugx.org/mojopollo/laravel-json-schema/v/stable)](https://packagist.org/packages/mojopollo/laravel-json-schema) 7 | [![Latest Unstable Version](https://poser.pugx.org/mojopollo/laravel-json-schema/v/unstable)](https://packagist.org/packages/mojopollo/laravel-json-schema) 8 | [![License](https://poser.pugx.org/mojopollo/laravel-json-schema/license)](https://packagist.org/packages/mojopollo/laravel-json-schema) 9 | [![Total Downloads](https://poser.pugx.org/mojopollo/laravel-json-schema/downloads)](https://packagist.org/packages/mojopollo/laravel-json-schema) 10 | 11 | Create all your migrations and models from one JSON schema file. 12 | This package allows you to define your entire [Laravel](https://github.com/laravel/laravel) database schema in one JSON file then generates all the necessary migration files. 13 | Makes use of Jeffrey Way's [Extended Generators](https://github.com/laracasts/Laravel-5-Generators-Extended). 14 | 15 | ![preview-01](https://cloud.githubusercontent.com/assets/1254915/14058470/9834f110-f2de-11e5-9c50-4ceffa0c9f88.jpg) 16 | 17 | - [Versions](#versions) 18 | - [Installation](#installation) 19 | - [Usage](#usage) 20 | - [Create your schema in JSON](#usage-create) 21 | - [Generate your migrations](#usage-generate) 22 | - [Pivot tables](#usage-pivot) 23 | - [Undo](#usage-undo) 24 | - [Validation](#usage-validation) 25 | - [JSON File Examples](#json-file-examples) 26 | 27 | 28 | ## Versions 29 | 30 | For Laravel 5.4.x and above use laravel-json-schema tag 5.4.x. You might need to set your composer.json minimum-stability to `dev` : 31 | ``` 32 | "minimum-stability": "dev" 33 | ``` 34 | 35 | For Laravel 5.3.x and below use laravel-json-schema tag 1.x.x 36 | 37 | 38 | ## Installation 39 | 40 | #### Step 1: Add package via composer 41 | 42 | Add this package to your `composer.json` file with the following command 43 | 44 | ```bash 45 | composer require mojopollo/laravel-json-schema --dev 46 | ``` 47 | 48 | #### Step 2: Add the service providers 49 | 50 | Add the following 2 service providers to your local environment only, by modifying your ```app/Providers/AppServiceProvider.php``` as so: 51 | ```php 52 | public function register() 53 | { 54 | if ($this->app->environment() == 'local') { 55 | $this->app->register('Mojopollo\Schema\MakeMigrationJsonServiceProvider'); 56 | $this->app->register('Laracasts\Generators\GeneratorsServiceProvider'); 57 | } 58 | } 59 | ``` 60 | 61 | 62 | ## Usage 63 | 64 | 65 | 66 | #### Create your schema in JSON 67 | 68 | Create your JSON schema file and save as ```schema.json``` for example: 69 | 70 | ```json 71 | { 72 | "users": { 73 | "email": "string:unique", 74 | "password": "string:index", 75 | "first_name": "string:nullable", 76 | "last_name": "string:nullable", 77 | "last_active_at": "timestamp:nullable:index" 78 | }, 79 | "categories": { 80 | "name": "string:unique" 81 | } 82 | } 83 | ``` 84 | 85 | 86 | 87 | #### Generate your migrations 88 | 89 | If you have your JSON file, you can now generate all your migrations, using the ```--file=``` option to specify where the JSON file is located: 90 | 91 | ```bash 92 | php artisan make:migration:json --file=schema.json 93 | ``` 94 | 95 | After this command executes you will see all the newly created migration files, example output: 96 | 97 | ```bash 98 | Model created successfully. 99 | Migration created successfully. 100 | Model created successfully. 101 | Migration created successfully. 102 | The following files have been created: 103 | app/CartItem.php 104 | app/Category.php 105 | database/migrations/2016_03_13_231727_create_categories_table.php 106 | database/migrations/2016_03_13_231728_create_tags_table.php 107 | ``` 108 | 109 | If you have a extensive long schema json file and want to only generate specific tables or migrations from the schema, you would do the following: 110 | 111 | ```bash 112 | php artisan make:migration:json --file=schema.json --only=categories,tags 113 | ``` 114 | 115 | In the above example, the tables or migrations named "categories" and "tags" will be generated and all other tables/migrations will be ignored. 116 | 117 | 118 | 119 | #### Pivot tables 120 | 121 | If you need to generate a pivot table, you will append ```_pivot``` to your migration name, for example: 122 | 123 | ```json 124 | { 125 | "posts_tags_pivot": null 126 | } 127 | ``` 128 | 129 | This will create a pivot table migration for the tables ```posts``` and ```tags``` 130 | 131 | 132 | 133 | #### Undo 134 | 135 | To undo and delete all files that where previously generated with the json file that was used, example: 136 | 137 | ```bash 138 | php artisan make:migration:json --file=schema.json --undo 139 | ``` 140 | 141 | What this will do is look for the ```schema.json.undo.json``` file if it exists, read the contents and remove all files that where generated, example output: 142 | 143 | ```bash 144 | Deleting files: 145 | Deleted: app/CartItem.php 146 | Deleted: app/Category.php 147 | Deleted: database/migrations/2016_03_13_231727_create_categories_table.php 148 | Deleted: database/migrations/2016_03_13_231728_create_tags_table.php 149 | ``` 150 | 151 | If you prefer not to create a "undo file" in the same directory as the source json file, use the ```--disableundo``` option at the time of migration generation: 152 | 153 | ```bash 154 | php artisan make:migration:json --file=schema.json --disableundo 155 | ``` 156 | 157 | This will prevent the creation of a undo file. 158 | 159 | 160 | 161 | #### Validation 162 | 163 | To check your json file for valid json syntax and schema validation (column type definitions and column type modifiers checks): 164 | 165 | ```bash 166 | php artisan make:migration:json --file=schema.json --validate 167 | ``` 168 | 169 | Note: this does not generate any migration files and will just check if you misspelled any field schema definitions 170 | 171 | 172 | 173 | ## JSON File Examples 174 | 175 | 176 | #### Using table names or migration names 177 | 178 | You can use table names or use a migration name that [Extended Generators](https://github.com/laracasts/Laravel-5-Generators-Extended) will understand. 179 | 180 | For example: 181 | 182 | ```json 183 | { 184 | "users": { 185 | "email": "string:unique", 186 | "password": "string:index" 187 | } 188 | } 189 | ``` 190 | 191 | Is the same as: 192 | 193 | ```json 194 | { 195 | "create_users_table": { 196 | "email": "string:unique", 197 | "password": "string:index" 198 | } 199 | } 200 | ``` 201 | 202 | 203 | #### Putting it all together 204 | 205 | You can now get crazy with defining your entire database schema and having the benefit of seeing it all in one file. 206 | As you have seen we can ```--undo``` to remove all previously generated files from the last command then make edits to our JSON file, 207 | validate the syntax with ```--validate``` and then generate it all over again. 208 | One word: **WOW**. :) 209 | 210 | ```json 211 | { 212 | "users": { 213 | "email": "string:unique", 214 | "password": "string:index" 215 | }, 216 | 217 | "create_cats_table": { 218 | "name": "string:unique" 219 | }, 220 | 221 | "remove_user_id_from_posts_table": { 222 | "name": "user_id:integer" 223 | }, 224 | 225 | "posts_tags_pivot": null 226 | } 227 | ``` 228 | -------------------------------------------------------------------------------- /tests/MakeMigrationJsonTest.php: -------------------------------------------------------------------------------- 1 | makeMigrationJson = new MakeMigrationJson; 33 | 34 | // Set json file path 35 | $this->jsonFilePath = 'tests/json/test.json'; 36 | } 37 | 38 | /** 39 | * This will run at the end of every test method 40 | */ 41 | public function tearDown() 42 | { 43 | // Parent teardown 44 | parent::tearDown(); 45 | 46 | // Unset Arr class 47 | $this->makeMigrationJson = null; 48 | } 49 | 50 | /** 51 | * Test jsonFileToArray() 52 | * 53 | * @return array $jsonArray 54 | */ 55 | public function testJsonFileToArray() 56 | { 57 | // Execute method 58 | $jsonArray = $this->makeMigrationJson->jsonFileToArray($this->jsonFilePath); 59 | 60 | // Make sure contents are of type array 61 | $this->assertTrue(is_array($jsonArray), 'json file contents do not return an array'); 62 | 63 | // Return json array for more testing 64 | return $jsonArray; 65 | } 66 | 67 | /** 68 | * Test parseSchema() 69 | * 70 | * @depends testJsonFileToArray 71 | * @return void 72 | */ 73 | public function testParseSchema(array $jsonArray) 74 | { 75 | // Execute method 76 | $results = $this->makeMigrationJson->parseSchema($jsonArray); 77 | 78 | // Make sure we "users" got turned into "create_users_table" and has values 79 | $this->assertFalse(empty($results['create_users_table']), '"users" was not converted to "create_users_table"'); 80 | 81 | // Make sure "remove_city_from_users_table" has been left intact 82 | $this->assertTrue( 83 | isset($results['remove_city_from_users_table']), 84 | '"remove_city_from_users_table" should be in the json array but it is not set' 85 | ); 86 | 87 | // Make sure our pivot test schema definition got correctly set 88 | $this->assertTrue(isset($results['posts_tags_pivot']), 'migration "posts_tags_pivot" is missing'); 89 | 90 | // Make sure our pivot test schema definition has the table names properly parsed out 91 | $this->assertEquals($results['posts_tags_pivot'], 'posts tags'); 92 | } 93 | 94 | /** 95 | * Test parseSchema() with $only parameter 96 | * 97 | * @return void 98 | */ 99 | public function testParseSchemaWithOnlyParameter() 100 | { 101 | // Set json array 102 | $jsonArray = [ 103 | 'dogs' => [ 104 | 'field1' => 'schema1', 105 | ], 106 | 'cats' => [ 107 | 'field1' => 'schema1', 108 | ], 109 | 'birds' => [ 110 | 'field1' => 'schema1', 111 | ], 112 | ]; 113 | 114 | // Set only array 115 | $only = [ 116 | 'cats', 117 | 'birds' 118 | ]; 119 | 120 | // Execute method 121 | $results = $this->makeMigrationJson->parseSchema($jsonArray, $only); 122 | 123 | // Expected results 124 | $expected = [ 125 | 'create_cats_table' => 'field1:schema1', 126 | 'create_birds_table' => 'field1:schema1', 127 | ]; 128 | 129 | // We should only get back cats and birds 130 | $this->assertEquals($results, $expected); 131 | } 132 | 133 | /** 134 | * Test setMigrationName() 135 | * 136 | * @return void 137 | */ 138 | public function testSetMigrationName() 139 | { 140 | // Make sure table names are converted into their proper migration name 141 | $tableName = 'users'; 142 | $this->assertEquals($this->makeMigrationJson->setMigrationName($tableName), "create_{$tableName}_table"); 143 | 144 | // Make sure migration names are not converted 145 | $tableName = 'remove_city_from_users_table'; 146 | $this->assertEquals($this->makeMigrationJson->setMigrationName($tableName), $tableName); 147 | } 148 | 149 | /** 150 | * Test isValidColumnType() 151 | * 152 | * @return void 153 | */ 154 | public function testIsValidColumnType() 155 | { 156 | // The following column type should fail 157 | $types = [ 158 | 'purpleRain', 159 | 'masterBlaster', 160 | ]; 161 | foreach ($types as $type) { 162 | $this->assertFalse( 163 | $this->makeMigrationJson->isValidColumnType($type), 164 | "'{$type}' should not be a valid column type" 165 | ); 166 | } 167 | 168 | // The following column types should pass 169 | $types = [ 170 | 'string', 171 | 'integer', 172 | 'bigInteger', 173 | 'morphs', 174 | 'mediumText', 175 | 'timestamp', 176 | ]; 177 | foreach ($types as $type) { 178 | $this->assertTrue( 179 | $this->makeMigrationJson->isValidColumnType($type), 180 | "'{$type}' should be a valid column type" 181 | ); 182 | } 183 | } 184 | 185 | /** 186 | * Test validateSchema() 187 | * 188 | * @return void 189 | */ 190 | public function testValidateSchema() 191 | { 192 | // Set the json array schema 193 | $schemaArray = [ 194 | 'dogs' => [ 195 | 'name' => 'string:unique', 196 | 'paws' => 'yesTheyHaveThemSometimes:index', 197 | 'canines' => 'boolean', 198 | 'hair' => 'string(50):index', 199 | 'ears_invalid' => 'string:thisIsMyInvalidModifier', 200 | 'ears_valid' => "string:after('id')", 201 | ], 202 | 'create_cats_table' => [ 203 | 'hair' => 'boolean', 204 | ], 205 | 'posts_tags_pivot' => null, 206 | ]; 207 | 208 | // Validate schema 209 | $errors = $this->makeMigrationJson->validateSchema($schemaArray); 210 | 211 | // The 'paws' section should come back with invalid column type error 212 | $this->assertTrue( 213 | isset($errors['dogs']['paws']['columnType']), 214 | 'columnType: "paws" was supposed to come back with a column type error, instead we got: ' 215 | . json_encode($errors) 216 | ); 217 | 218 | // The 'ears_invalid' section should come back with invalid column type error 219 | $this->assertTrue( 220 | isset($errors['dogs']['ears_invalid']['columnModifier']), 221 | 'columnModifier: "ears_invalid" was supposed to come back with a column modifier error, instead we got: ' 222 | . json_encode($errors) 223 | ); 224 | 225 | // The 'hair' section should not come back with errors because of its optional column type parameters 226 | $this->assertFalse( 227 | isset($errors['dogs']['hair']), 228 | 'columnType: "hair:string(50):index" should be allowed to be validated as a "string", not as "string(50)": ' 229 | ); 230 | 231 | // The 'ears_valid' section should not come back with errors because it is a valid column modifier or index 232 | $this->assertFalse( 233 | isset($errors['dogs']['ears_valid']), 234 | 'columnType: "ears_valid" should not come back with errors because it is a valid column modifier or index' 235 | ); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/MakeMigrationJson.php: -------------------------------------------------------------------------------- 1 | filesystem = new Filesystem; 31 | 32 | // Instantiate Blueprint class 33 | $this->blueprint = new Blueprint($table = 'idk_my_bff_jill'); 34 | } 35 | 36 | /** 37 | * Grabs a json file and returns a array 38 | * 39 | * @param string $path The path to the json file 40 | * @return array The parsed array 41 | */ 42 | public function jsonFileToArray($path) 43 | { 44 | // Get file contents 45 | $contents = $this->filesystem->get($path); 46 | 47 | // Return array 48 | return json_decode($contents, true); 49 | } 50 | 51 | /** 52 | * Parses the schema from the json array into a readable format laravel generators extended understand 53 | * 54 | * @param array $data The array containing the json output from file 55 | * @param array $only (optional) array containing specific table/migration names to retrieve from the json file 56 | * @return array The finished parsed schema for use with generators extended 57 | */ 58 | public function parseSchema(array $data, array $only = []) 59 | { 60 | // Final schema 61 | $schema = []; 62 | 63 | // For every table 64 | foreach ($data as $tableName => $fields) { 65 | // If the --only option was used 66 | if (! empty($only)) { 67 | // If this tableName is not existing in the only array 68 | if (! in_array($tableName, $only)) { 69 | // Go to next table 70 | continue 1; 71 | } 72 | } 73 | 74 | // Set migration name / class name 75 | $migrationName = $this->setMigrationName($tableName); 76 | 77 | // Check if this is a pivot table definition 78 | if (substr($migrationName, -6) === '_pivot') { 79 | // Get table names 80 | $tables = explode('_', $migrationName, 3); 81 | 82 | // Add to the schema array 83 | $schema[$migrationName] = "{$tables[0]} {$tables[1]}"; 84 | 85 | // Go to next table 86 | continue 1; 87 | } 88 | 89 | // For every field 90 | foreach ($fields as $fieldName => $fieldSchema) { 91 | // Add to the schema array 92 | $schema[$migrationName][] = "{$fieldName}:{$fieldSchema}"; 93 | } 94 | 95 | // Join all fields for this migration in a single line 96 | $schema[$migrationName] = implode(', ', $schema[$migrationName]); 97 | } 98 | 99 | // Return final schema 100 | return $schema; 101 | } 102 | 103 | /** 104 | * Creates a migration name from a table name 105 | * or keeps it intact if a migration name has already been set 106 | * 107 | * @param string $tableName The original table name (Example: users, cats, create_users_table) 108 | * @return string The migration name 109 | */ 110 | public function setMigrationName($tableName) 111 | { 112 | // Check if "_table" is already supplied or if this is a "pivot" table 113 | if (strpos($tableName, '_table') !== false || substr($tableName, -6) === '_pivot') { 114 | // Since the migration name has already been set, return it intact 115 | return $tableName; 116 | } 117 | 118 | // Create migration name 119 | return "create_{$tableName}_table"; 120 | } 121 | 122 | /** 123 | * Validates the schema from the json array and returns an error of syntax errors if any are found 124 | * 125 | * @param array $data The array containing the json output from file 126 | * @return array Any errors identified for every field schema 127 | * @see https://laravel.com/docs/5.2/migrations#creating-columns 128 | * @see https://laravel.com/docs/5.2/migrations#creating-indexes 129 | */ 130 | public function validateSchema(array $data) 131 | { 132 | // Error array 133 | $errors = []; 134 | 135 | // Get allowed column modifiers and indexes 136 | $validModifiersAndIndexes = array_merge($this->getColumnIndexes(), $this->getColumnModifiers()); 137 | 138 | // For every table 139 | foreach ($data as $tableName => $fields) { 140 | // If there is no fields here do not continue 141 | if (empty($fields)) { 142 | // Go to next 143 | continue 1; 144 | } 145 | 146 | // For every field 147 | foreach ($fields as $fieldName => $fieldSchema) { 148 | // Split field schema 149 | $fieldSchema = explode(':', $fieldSchema); 150 | 151 | // Assign column type 152 | $columnType = $this->parseProperty($fieldSchema[0]); 153 | 154 | // Assign all modifiers and indexes 155 | // only if there is a count of 2 or more 156 | $columnModifiersAndIndexes = count($fieldSchema) > 1 ? array_slice($fieldSchema, 1) : []; 157 | 158 | // Check for valid column type 159 | if ($this->isValidColumnType($columnType) === false) { 160 | // Keep the json array structure and report error 161 | $errors[$tableName][$fieldName]['columnType'] = "'{$columnType}' is not a valid column type"; 162 | } 163 | 164 | // Check for valid column modifiers 165 | foreach ($columnModifiersAndIndexes as $modifierOrIndex) { 166 | // If this $modifierOrIndex is not in our $validModifiersAndIndexes array 167 | // report error 168 | if (! in_array($this->parseProperty($modifierOrIndex), $validModifiersAndIndexes)) { 169 | // Keep the json array structure and report error 170 | $errors[$tableName][$fieldName]['columnModifier'] 171 | = "'{$modifierOrIndex}' is not a valid column modifier"; 172 | } 173 | } 174 | } 175 | } 176 | 177 | // Return the errors array 178 | return $errors; 179 | } 180 | 181 | /** 182 | * Parses the property without the parameters 183 | * in this method, "string" and "string(50)" should both return as "string" 184 | * 185 | * @param string $type Example: "string", "string(50)", etc 186 | * @return string the parsed column type name 187 | */ 188 | public function parseProperty($type) 189 | { 190 | // Remove any parameters to this column type 191 | $type = explode('(', $type); 192 | $type = trim($type[0]); 193 | 194 | // Return column type 195 | return $type; 196 | } 197 | 198 | /** 199 | * Checks if supplied string argument is a valid column type 200 | * 201 | * @param string $type Example: string, integer, text, timestamp, etc 202 | * @return boolean Returns true if valid, otherwise false 203 | * @see https://laravel.com/docs/5.2/migrations#creating-columns 204 | */ 205 | public function isValidColumnType($type) 206 | { 207 | // Check if this is a valid method in the Blueprint class 208 | return method_exists($this->blueprint, $type); 209 | } 210 | 211 | /** 212 | * Get an array of valid column indexes 213 | * 214 | * @return array list of the possible column indexes 215 | * @see https://laravel.com/docs/5.2/migrations#creating-indexes 216 | * @see vendor/illuminate/database/Schema/Blueprint.php @ addFluentIndexes() 217 | * @see https://github.com/laracasts/Laravel-5-Generators-Extended#user-content-foreign-constraints 218 | */ 219 | public function getColumnIndexes() 220 | { 221 | // Set column indexes from the laravel class 222 | $indexes = ['primary', 'unique', 'index']; 223 | 224 | // Add the extended generators foreign keyword "bit of sugar" 225 | // https://github.com/laracasts/Laravel-5-Generators-Extended#user-content-foreign-constraints 226 | $indexes[] = 'foreign'; 227 | 228 | // Return column indexes 229 | return $indexes; 230 | } 231 | 232 | /** 233 | * Get an array of column modifiers from the MySqlGrammar::modifiers property 234 | * 235 | * @return array Example: unsigned, charset, collate, ... 236 | */ 237 | public function getColumnModifiers() 238 | { 239 | // Create reflection class for MySqlGrammar 240 | $class = new \ReflectionClass(new MySqlGrammar); 241 | 242 | // Get our protected modifiers property 243 | $property = $class->getProperty('modifiers'); 244 | 245 | // Set properties to be publicly accessible 246 | $property->setAccessible(true); 247 | 248 | // Return MySqlGrammar::modifiers array in lowercase 249 | return array_map('strtolower', $property->getValue(new MySqlGrammar)); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/Commands/MakeMigrationJsonCommand.php: -------------------------------------------------------------------------------- 1 | makeMigrationJson = $makeMigrationJson; 77 | 78 | // Set Filesystem instance 79 | $this->filesystem = $filesystem; 80 | } 81 | 82 | /** 83 | * Execute the console command. 84 | * 85 | * @return mixed 86 | */ 87 | public function fire() 88 | { 89 | // If json file is specified 90 | if ($this->filePath = $this->option('file')) { 91 | // Set file path directory 92 | $this->filePathDirectory = dirname($this->filePath); 93 | 94 | // Set undo file path 95 | $this->undoFilePath = $this->filePath . '.undo.json'; 96 | 97 | // If the undo option was invoked 98 | if ($this->option('undo')) { 99 | // Undo previous file generation 100 | $this->undo(); 101 | 102 | // End method execution 103 | return; 104 | } 105 | 106 | // If the validate option was invoked 107 | if ($this->option('validate')) { 108 | // Validate the json file 109 | $this->validate(); 110 | 111 | // End method execution 112 | return; 113 | } 114 | 115 | // Generate the migrations 116 | $this->makeJsonMigration(); 117 | 118 | // If disableundo is not active 119 | if ($this->option('disableundo') === false) { 120 | // Create a undo file 121 | $this->createUndoFile(); 122 | } 123 | } 124 | 125 | // If no action options where chosen 126 | if ($this->filePath === null && $this->option('undo') === false && $this->option('validate') === false) { 127 | // Show help screen 128 | $this->call('help', [ 129 | 'command_name' => $this->name, 130 | ]); 131 | } 132 | } 133 | 134 | /** 135 | * Make the json migration 136 | * 137 | * @return void 138 | */ 139 | protected function makeJsonMigration() 140 | { 141 | // Set start time of file generation 142 | $this->startTime = time(); 143 | 144 | // Get json array from file 145 | $jsonArray = $this->makeMigrationJson->jsonFileToArray($this->filePath); 146 | 147 | // Parse only option 148 | $only = []; 149 | if (! empty($this->option('only'))) { 150 | $only = explode(',', $this->option('only')); 151 | $only = array_map('trim', $only); 152 | } 153 | 154 | // Parse json and get schema 155 | $schema = $this->makeMigrationJson->parseSchema($jsonArray, $only); 156 | 157 | // For every migration in the schema 158 | foreach ($schema as $migrationName => $fieldSchema) { 159 | // Check if this migration is a pivot table 160 | if (substr($migrationName, -6) === '_pivot') { 161 | // Get tables 162 | $tables = explode(' ', $fieldSchema, 3); 163 | 164 | // Invoke the extended generator command for pivot tables 165 | $this->call('make:migration:pivot', [ 166 | 'tableOne' => $tables[0], 167 | 'tableTwo' => $tables[1], 168 | ]); 169 | 170 | // Go to next migration 171 | continue 1; 172 | } 173 | 174 | // Invoke the extended generator command 175 | $this->call('make:migration:schema', [ 176 | 'name' => $migrationName, 177 | '--schema' => $fieldSchema, 178 | ]); 179 | 180 | // wait 1 second in-between schemas to run the insequence later with "php artisan migrate" 181 | sleep(1); 182 | } 183 | // $this->info(var_export($schema, true)); 184 | } 185 | 186 | /** 187 | * Creates the undo file 188 | * 189 | * @return void 190 | */ 191 | protected function createUndoFile() 192 | { 193 | // The generated files 194 | $generatedFiles = []; 195 | 196 | // Scan folders for generated files 197 | foreach (['app', 'database/migrations'] as $folder) { 198 | // For every file inside this folder 199 | foreach ($this->filesystem->files($folder) as $file) { 200 | // If lastModified time of this file is greater or equal to $this->startTime 201 | if ($this->filesystem->lastModified($file) >= $this->startTime) { 202 | // Add this file to our generated files array 203 | $generatedFiles[] = $file; 204 | } 205 | } 206 | } 207 | 208 | // If we do not have any generated files 209 | if (empty($generatedFiles)) { 210 | // Show error message and end method execution 211 | $this->error('No generated files created'); 212 | return; 213 | } 214 | 215 | // Output generated files to console 216 | $this->info("The following files have been created:"); 217 | foreach ($generatedFiles as $generatedFile) { 218 | $this->info(" {$generatedFile}"); 219 | } 220 | 221 | // Save $generatedFiles to undo file if directory is writeable 222 | if ($this->filesystem->isWritable($this->filePathDirectory)) { 223 | // Write undo json file 224 | $this->filesystem->put( 225 | $this->undoFilePath, 226 | json_encode($generatedFiles, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT) 227 | ); 228 | } else { 229 | // Show error that file could not be created 230 | $this->error('Could not create undo file, not enough permissions perhaps?: ' . $this->undoFilePath); 231 | } 232 | } 233 | 234 | /** 235 | * Perform undo action 236 | * 237 | * @return void 238 | */ 239 | protected function undo() 240 | { 241 | // Delete status 242 | $deleteCompleted = true; 243 | 244 | // Get files from the json undo file 245 | $files = json_decode($this->filesystem->get($this->undoFilePath), true); 246 | 247 | // For each file 248 | $this->info('Deleting files:'); 249 | foreach ($files as $file) { 250 | // If this file can be deleted 251 | if ($this->filesystem->isWritable($file)) { 252 | // Delete it 253 | $this->filesystem->delete($file); 254 | $this->info(" Deleted: {$file}"); 255 | } else { 256 | // Set status 257 | $deleteCompleted = false; 258 | 259 | // Show error 260 | $this->error('Could not delete: ' . $file); 261 | } 262 | } 263 | 264 | // if the delete prccess finished successfully 265 | if ($deleteCompleted) { 266 | // Delete undo file 267 | $this->filesystem->delete($this->undoFilePath); 268 | } 269 | } 270 | 271 | /** 272 | * Validate the json file and console report any issues 273 | * 274 | * @return void 275 | */ 276 | protected function validate() 277 | { 278 | // Get json array from file 279 | $jsonArray = $this->makeMigrationJson->jsonFileToArray($this->filePath); 280 | 281 | // Check for invalid json 282 | if ($jsonArray === null) { 283 | // Display error message 284 | $this->error('Invalid JSON detected: Check that your json file does not contain invalid syntax: ' 285 | . $this->filePath); 286 | 287 | // End further execution 288 | return; 289 | } 290 | 291 | // Check for data existence 292 | if (empty($jsonArray)) { 293 | // Display error message 294 | $this->error('No data found in json file: It seems you have no data in: ' . $this->filePath); 295 | 296 | // End further execution 297 | return; 298 | } 299 | 300 | // Validate 301 | $errors = $this->makeMigrationJson->validateSchema($jsonArray); 302 | 303 | // If no errors where found 304 | if (empty($errors)) { 305 | // Display confirmation message 306 | $this->info('No validation errors where found, congrats!'); 307 | 308 | // End further execution 309 | return; 310 | } 311 | 312 | // Report results 313 | foreach ($errors as $tableName => $fields) { 314 | // For every field 315 | foreach ($fields as $fieldName => $fieldProperties) { 316 | // For every field property 317 | foreach ($fieldProperties as $property) { 318 | // Show error 319 | $this->error($property); 320 | 321 | // Show json reference 322 | $this->error("In section: " . json_encode([ 323 | $tableName => [ 324 | $fieldName => $jsonArray[$tableName][$fieldName] 325 | ] 326 | ], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); 327 | } 328 | } 329 | } 330 | } 331 | 332 | /** 333 | * Get the console command arguments. 334 | * 335 | * @return array 336 | */ 337 | protected function getArguments() 338 | { 339 | // No arguments here 340 | return []; 341 | } 342 | 343 | /** 344 | * Get the console command options. 345 | * 346 | * @return array 347 | */ 348 | protected function getOptions() 349 | { 350 | // Return all available options 351 | return [ 352 | ['file', null, InputOption::VALUE_OPTIONAL, 'The file path to the JSON schema', null], 353 | ['only', null, InputOption::VALUE_OPTIONAL, 'Filter by migration name, example: --only=cats,birds', null], 354 | ['validate', null, InputOption::VALUE_NONE, 'Validate schema in json file and report any problems'], 355 | ['undo', null, InputOption::VALUE_NONE, 'Undo and remove all files generated from last command'], 356 | ['disableundo', null, InputOption::VALUE_NONE, 'Do not create a "undo" file'], 357 | ]; 358 | } 359 | } 360 | --------------------------------------------------------------------------------