├── .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 | [](https://travis-ci.org/mojopollo/laravel-json-schema)
5 | [](https://coveralls.io/github/mojopollo/laravel-json-schema?branch=master)
6 | [](https://packagist.org/packages/mojopollo/laravel-json-schema)
7 | [](https://packagist.org/packages/mojopollo/laravel-json-schema)
8 | [](https://packagist.org/packages/mojopollo/laravel-json-schema)
9 | [](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 | 
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 |
--------------------------------------------------------------------------------