[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [bennett-treptow]\ncustom: ['https://buymeacoffee.com/btreptow']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"[BUG]\"\nlabels: ''\nassignees: ''\n\n---\n\n**Package Version**\nWhat version are you running? 2.2.*, 3.*\n\n**Database Version**\nWhat database driver are you using? And what version is that database?\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nPlease include any stack traces and applicable .env / config changes you've made\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/workflows/php.yml",
    "content": "name: PHP Composer\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\njobs:\n  build:\n    strategy:\n      matrix:\n        php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4']\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: ${{ matrix.php }}\n\n      - name: Cache Composer packages\n        id: composer-cache\n        uses: actions/cache@v4\n        with:\n          path: vendor\n          key: \"${{ runner.os }}-php-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}\"\n          restore-keys: |\n            ${{ runner.os }}-php-${{ matrix.php }}-\n\n      - name: Remove pint\n        run: composer remove laravel/pint --dev --no-update\n\n      - name: Install dependencies\n        run: composer install --prefer-dist --no-progress\n\n      - name: Run test suite\n        run: composer run test\n"
  },
  {
    "path": ".gitignore",
    "content": "vendor/\n.idea\n.phpunit.result.cache\n.php_cs.cache\ncomposer.lock"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Version 3.1.6\n### New Modifier\n`useCurrentOnUpdate` has been implemented\n### Bugfix\nIssue #27 - `useCurrent` on `timestamps()` method fix\n\n# Version 3.1.3\n### [Timestamp:format] Removal\nThe [Timestamp:format] token for file names has been removed. Migration file names require that [Timestamp] be at the beginning in that specific format. Any other format would cause the migrations to not be loaded.\n\n\n# Version 3.1.0\n### Environment Variables\nNew environment variables:\n\n| Key | Default Value | Allowed Values | Description |\n| --- | ------------- | -------------- | ----------- |\n| LMG_SKIP_VIEWS | false | boolean | When true, skip all views |\n| LMG_SKIPPABLE_VIEWS | '' | comma delimited string | The views to be skipped |\n| LMG_MYSQL_SKIPPABLE_VIEWS | null | comma delimited string | The views to be skipped when driver is `mysql` |\n| LMG_SQLITE_SKIPPABLE_VIEWS | null | comma delimited string | The views to be skipped when driver is `sqlite` |\n| LMG_PGSQL_SKIPPABLE_VIEWS | null | comma delimited string | The views to be skipped when driver is `pgsql` |\n| LMG_SQLSRV_SKIPPABLE_VIEWS | null | comma delimited string | The views to be skipped when driver is `sqlsrv` |\n\n# Version 3.0.0\n\n### Run after migrations\nWhen `LMG_RUN_AFTER_MIGRATIONS` is set to true, after running any of the `artisan migrate` commands, the `generate:migrations` command will be run using all the default options for the command. It will only run when the app environment is `local`.\n\n### Environment Variables\nNew environment variables to replace config updates:\n\n| Key | Default Value | Allowed Values | Description |\n| --- | ------------- | -------------- | ----------- |\n| LMG_RUN_AFTER_MIGRATIONS | false | boolean | Whether or not the migration generator should run after migrations have completed. |\n| LMG_CLEAR_OUTPUT_PATH | false | boolean | Whether or not to clear out the output path before creating new files |\n| LMG_TABLE_NAMING_SCHEME | [Timestamp]_create_[TableName]_table.php | string | The string to be used to name table migration files |\n| LMG_VIEW_NAMING_SCHEME | [Timestamp]_create_[ViewName]_view.php | string | The string to be used to name view migration files |\n| LMG_OUTPUT_PATH | tests/database/migrations | string | The path (relative to the root of your project) to where the files will be output to |\n| LMG_SKIPPABLE_TABLES | migrations | comma delimited string | The tables to be skipped |\n| LMG_PREFER_UNSIGNED_PREFIX | true | boolean | When true, uses `unsigned` variant methods instead of the `->unsigned()` modifier. |\n| LMG_USE_DEFINED_INDEX_NAMES | true | boolean | When true, uses index names defined by the database as the name parameter for index methods |\n| LMG_USE_DEFINED_FOREIGN_KEY_INDEX_NAMES | true | boolean | When true, uses foreign key index names defined by the database as the name parameter for foreign key methods |\n| LMG_USE_DEFINED_UNIQUE_KEY_INDEX_NAMES | true | boolean | When true, uses unique key index names defined by the database as the name parameter for the `unique` methods |\n| LMG_USE_DEFINED_PRIMARY_KEY_INDEX_NAMES | true | boolean | When true, uses primary key index name defined by the database as the name parameter for the `primary` method |\n| LMG_MYSQL_TABLE_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_TABLE_NAMING_SCHEME when the database driver is `mysql`. |\n| LMG_MYSQL_VIEW_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_VIEW_NAMING_SCHEME when the database driver is `mysql`. |\n| LMG_MYSQL_OUTPUT_PATH | null | ?boolean | When not null, this setting will override LMG_OUTPUT_PATH when the database driver is `mysql`. |\n| LMG_MYSQL_SKIPPABLE_TABLES | null | ?boolean | When not null, this setting will override LMG_SKIPPABLE_TABLES when the database driver is `mysql`. |\n| LMG_SQLITE_TABLE_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_TABLE_NAMING_SCHEME when the database driver is `sqlite`. |\n| LMG_SQLITE_VIEW_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_VIEW_NAMING_SCHEME when the database driver is `sqlite`. |\n| LMG_SQLITE_OUTPUT_PATH | null | ?boolean | When not null, this setting will override LMG_OUTPUT_PATH when the database driver is `sqlite`. |\n| LMG_SQLITE_SKIPPABLE_TABLES | null | ?boolean | When not null, this setting will override LMG_SKIPPABLE_TABLES when the database driver is `sqlite`. |\n| LMG_PGSQL_TABLE_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_TABLE_NAMING_SCHEME when the database driver is `pgsql`. |\n| LMG_PGSQL_VIEW_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_VIEW_NAMING_SCHEME when the database driver is `pgsql`. |\n| LMG_PGSQL_OUTPUT_PATH | null | ?boolean | When not null, this setting will override LMG_OUTPUT_PATH when the database driver is `pgsql`. |\n| LMG_PGSQL_SKIPPABLE_TABLES | null | ?boolean | When not null, this setting will override LMG_SKIPPABLE_TABLES when the database driver is `pgsql`. |\n| LMG_SQLSRV_TABLE_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_TABLE_NAMING_SCHEME when the database driver is `sqlsrc`. |\n| LMG_SQLSRV_VIEW_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_VIEW_NAMING_SCHEME when the database driver is `sqlsrv`. |\n| LMG_SQLSRV_OUTPUT_PATH | null | ?boolean | When not null, this setting will override LMG_OUTPUT_PATH when the database driver is `sqlsrv`. |\n| LMG_SQLSRV_SKIPPABLE_TABLES | null | ?boolean | When not null, this setting will override LMG_SKIPPABLE_TABLES when the database driver is `sqlsrv`. |\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Bennett Treptow\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Laravel Migration Generator\n![Latest Version on Packagist](https://img.shields.io/packagist/v/bennett-treptow/laravel-migration-generator.svg)\n\nGenerate migrations from existing database structures, an alternative to the schema dump provided by Laravel. A primary use case for this package would be a project that has many migrations that alter tables using `->change()` from doctrine/dbal that SQLite doesn't support and need a way to get table structures updated for SQLite to use in tests.\nAnother use case would be taking a project with a database and no migrations and turning that database into base migrations.\n\n# Installation\n```bash\ncomposer require --dev bennett-treptow/laravel-migration-generator\n```\n\n```bash\nphp artisan vendor:publish --provider=\"LaravelMigrationGenerator\\LaravelMigrationGeneratorProvider\"\n```\n# Lumen Installation  \n```bash  \ncomposer require --dev bennett-treptow/laravel-migration-generator\n```  \n  \nCopy config file from `vendor/bennett-treptow/laravel-migration-generator/config` to your Lumen config folder  \n  \nRegister service provider in bootstrap/app.php  \n```php  \n$app->register(\\LaravelMigrationGenerator\\LaravelMigrationGeneratorProvider::class);  \n```\n\n# Usage\n\nWhenever you have database changes or are ready to squash your database structure down to migrations, run:\n```bash\nphp artisan generate:migrations\n```\n\nBy default, the migrations will be created in `tests/database/migrations`. You can specify a different path with the `--path` option: \n```bash\nphp artisan generate:migrations --path=database/migrations\n```\n\nYou can specify the connection to use as the database with the `--connection` option:\n```bash\nphp artisan generate:migrations --connection=mysql2\n```\n\nYou can also clear the directory with the `--empty-path` option:\n```bash\nphp artisan generate:migrations --empty-path\n```\n\nThis command can also be run by setting the `LMG_RUN_AFTER_MIGRATIONS` environment variable to `true` and running your migrations as normal. This will latch into the `MigrationsEnded` event and run this command using the default options specified via your environment variables. Note: it will only run when your app environment is set to `local`.\n\n# Configuration\n\nWant to customize the migration stubs? Make sure you've published the vendor assets with the artisan command to publish vendor files above.\n\n## Environment Variables\n\n| Key | Default Value | Allowed Values | Description |\n| --- | ------------- | -------------- | ----------- |\n| LMG_RUN_AFTER_MIGRATIONS | false | boolean | Whether or not the migration generator should run after migrations have completed. |\n| LMG_CLEAR_OUTPUT_PATH | false | boolean | Whether or not to clear out the output path before creating new files. Same as specifying `--empty-path` on the command |\n| LMG_TABLE_NAMING_SCHEME | `[Timestamp]_create_[TableName]_table.php` | string | The string to be used to name table migration files |\n| LMG_VIEW_NAMING_SCHEME | `[Timestamp]_create_[ViewName]_view.php` | string | The string to be used to name view migration files |\n| LMG_OUTPUT_PATH | tests/database/migrations | string | The path (relative to the root of your project) to where the files will be output to. Same as specifying `--path=` on the command |\n| LMG_SKIPPABLE_TABLES | migrations | comma delimited string | The tables to be skipped |\n| LMG_SKIP_VIEWS | false | boolean | When true, skip all views |\n| LMG_SKIPPABLE_VIEWS | '' | comma delimited string | The views to be skipped |\n| LMG_SORT_MODE | 'foreign_key' | string | The sorting mode to be used. Options: `foreign_key` |\n| LMG_PREFER_UNSIGNED_PREFIX | true | boolean | When true, uses `unsigned` variant methods instead of the `->unsigned()` modifier. |\n| LMG_USE_DEFINED_INDEX_NAMES | true | boolean | When true, uses index names defined by the database as the name parameter for index methods |\n| LMG_USE_DEFINED_FOREIGN_KEY_INDEX_NAMES | true | boolean | When true, uses foreign key index names defined by the database as the name parameter for foreign key methods |\n| LMG_USE_DEFINED_UNIQUE_KEY_INDEX_NAMES | true | boolean | When true, uses unique key index names defined by the database as the name parameter for the `unique` methods |\n| LMG_USE_DEFINED_PRIMARY_KEY_INDEX_NAMES | true | boolean | When true, uses primary key index name defined by the database as the name parameter for the `primary` method |\n| LMG_WITH_COMMENTS | true | boolean | When true, export comment using `->comment()` method. |\n| LMG_USE_DEFINED_DATATYPE_ON_TIMESTAMP | false | boolean | When false, uses `->timestamps()` by mashing up `created_at` and `updated_at` regardless of  datatype defined by the database |\n| LMG_MYSQL_TABLE_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_TABLE_NAMING_SCHEME when the database driver is `mysql`. |\n| LMG_MYSQL_VIEW_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_VIEW_NAMING_SCHEME when the database driver is `mysql`. |\n| LMG_MYSQL_OUTPUT_PATH | null | ?boolean | When not null, this setting will override LMG_OUTPUT_PATH when the database driver is `mysql`. |\n| LMG_MYSQL_SKIPPABLE_TABLES | null | ?boolean | When not null, this setting will override LMG_SKIPPABLE_TABLES when the database driver is `mysql`. |\n| LMG_MYSQL_SKIPPABLE_VIEWS | null | comma delimited string | The views to be skipped when driver is `mysql` |\n| LMG_SQLITE_TABLE_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_TABLE_NAMING_SCHEME when the database driver is `sqlite`. |\n| LMG_SQLITE_VIEW_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_VIEW_NAMING_SCHEME when the database driver is `sqlite`. |\n| LMG_SQLITE_OUTPUT_PATH | null | ?boolean | When not null, this setting will override LMG_OUTPUT_PATH when the database driver is `sqlite`. |\n| LMG_SQLITE_SKIPPABLE_TABLES | null | ?boolean | When not null, this setting will override LMG_SKIPPABLE_TABLES when the database driver is `sqlite`. |\n| LMG_SQLITE_SKIPPABLE_VIEWS | null | comma delimited string | The views to be skipped when driver is `sqlite` |\n| LMG_PGSQL_TABLE_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_TABLE_NAMING_SCHEME when the database driver is `pgsql`. |\n| LMG_PGSQL_VIEW_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_VIEW_NAMING_SCHEME when the database driver is `pgsql`. |\n| LMG_PGSQL_OUTPUT_PATH | null | ?boolean | When not null, this setting will override LMG_OUTPUT_PATH when the database driver is `pgsql`. |\n| LMG_PGSQL_SKIPPABLE_TABLES | null | ?boolean | When not null, this setting will override LMG_SKIPPABLE_TABLES when the database driver is `pgsql`. |\n| LMG_PGSQL_SKIPPABLE_VIEWS | null | comma delimited string | The views to be skipped when driver is `pgsql` |\n| LMG_SQLSRV_TABLE_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_TABLE_NAMING_SCHEME when the database driver is `sqlsrc`. |\n| LMG_SQLSRV_VIEW_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_VIEW_NAMING_SCHEME when the database driver is `sqlsrv`. |\n| LMG_SQLSRV_OUTPUT_PATH | null | ?boolean | When not null, this setting will override LMG_OUTPUT_PATH when the database driver is `sqlsrv`. |\n| LMG_SQLSRV_SKIPPABLE_TABLES | null | ?boolean | When not null, this setting will override LMG_SKIPPABLE_TABLES when the database driver is `sqlsrv`. |\n| LMG_SQLSRV_SKIPPABLE_VIEWS | null | comma delimited string | The views to be skipped when driver is `sqlsrv` |\n\n## Stubs\nThere is a default stub for tables and views, found in `resources/stubs/vendor/laravel-migration-generator/`.\nEach database driver can be assigned a specific migration stub by creating a new stub file in `resources/stubs/vendor/laravel-migration-generator/` with a `driver`-prefix, e.g. `mysql-table.stub` for a MySQL specific table stub.\n\n## Stub Naming\nTable and view stubs can be named using the `LMG_(TABLE|VIEW)_NAMING_SCHEME` environment variables. Optionally, driver-specific naming schemes can be used as well by specifying `LMG_{driver}_TABLE_NAMING_SCHEME` environment vars using the same tokens. See below for available tokens that can be replaced.\n\n### Table Name Stub Tokens\nTable stubs have the following tokens available for the naming scheme:\n\n| Token | Example | Description |\n| ----- |-------- | ----------- |\n| `[TableName]` | users | Table's name, same as what is defined in the database |\n| `[TableName:Studly]` | Users | Table's name with `Str::studly()` applied to it (useful for standardizing table names if they are inconsistent) |\n| `[TableName:Lowercase]` | users | Table's name with `strtolower` applied to it (useful for standardizing table names if they are inconsistent) |\n| `[Timestamp]` | 2021_04_25_110000 | The standard migration timestamp format, at the time of calling the command: `Y_m_d_His` |\n| `[Index]` | 0 | The key of the migration in the sorted order, for use with enforcing a sort order |\n| `[IndexedEmptyTimestamp]` | 0000_00_00_000041 | The standard migration timestamp format, but filled with 0s and incremented by `[Index]` seconds |\n| `[IndexedTimestamp]` | 2021_04_25_110003 | The standard migration timestamp format, at the time of calling the command: `Y_m_d_His` incremented by `[Index]` seconds |\n\n\n### Table Schema Stub Tokens\nTable schema stubs have the following tokens available:\n\n| Token | Description |\n| ----- | ----------- |\n| `[TableName]` | Table's name, same as what is defined in the database |\n| `[TableName:Studly]` | Table's name with `Str::studly()` applied to it, for use with the class name |\n| `[TableUp]` | Table's `up()` function |\n| `[TableDown]` | Table's `down()` function |\n| `[Schema]` | The table's generated schema |\n\n\n### View Name Stub Tokens\nView stubs have the following tokens available for the naming scheme:\n\n| Token | Example | Description |\n| ----- |-------- | ----------- |\n| `[ViewName]` | user_earnings | View's name, same as what is defined in the database |\n| `[ViewName:Studly]` | UserEarnings | View's name with `Str::studly()` applied to it (useful for standardizing view names if they are inconsistent) |\n| `[ViewName:Lowercase]` | user_earnings | View's name with `strtolower` applied to it (useful for standardizing view names if they are inconsistent) |\n| `[Timestamp]` | 2021_04_25_110000 | The standard migration timestamp format, at the time of calling the command: `Y_m_d_His` |\n| `[Index]` | 0 | The key of the migration in the sorted order, for use with enforcing a sort order |\n| `[IndexedEmptyTimestamp]` | 0000_00_00_000041 | The standard migration timestamp format, but filled with 0s and incremented by `[Index]` seconds |\n| `[IndexedTimestamp]` | 2021_04_25_110003 | The standard migration timestamp format, at the time of calling the command: `Y_m_d_His` incremented by `[Index]` seconds |\n\n### View Schema Stub Tokens\nView schema stubs have the following tokens available:\n\n| Token | Description |\n| ----- | ----------- |\n| `[ViewName]` | View's name, same as what is defined in the database |\n| `[ViewName:Studly]` | View's name with `Str::studly()` applied to it, for use with the class name |\n| `[Schema]` | The view's schema |\n\n\n# Example Usage\n\nGiven a database structure for a `users` table of:\n```sql\nCREATE TABLE `users` (\n  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,\n  `username` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n  `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n  `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n  `first_name` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n  `last_name` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n  `timezone` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'America/New_York',\n  `location_id` int(10) unsigned NOT NULL,\n  `deleted_at` timestamp NULL DEFAULT NULL,\n  `remember_token` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n  `created_at` timestamp NULL DEFAULT NULL,\n  `updated_at` timestamp NULL DEFAULT NULL,\n  PRIMARY KEY (`id`),\n  KEY `users_username_index` (`username`),\n  KEY `users_first_name_index` (`first_name`),\n  KEY `users_last_name_index` (`last_name`),\n  KEY `users_email_index` (`email`),\n  KEY `fk_users_location_id_index` (`location_id`)\n  CONSTRAINT `users_location_id_foreign` FOREIGN KEY (`location_id`) REFERENCES `locations` (`id`) ON UPDATE CASCADE ON DELETE CASCADE\n) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci\n```\n\nA `tests/database/migrations/[TIMESTAMP]_create_users_table.php` with the following Blueprint would be created:\n```php\n<?php\n\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Database\\Migrations\\Migration;\n\nclass CreateUsersTable extends Migration\n{\n    /**\n     * Run the migrations.\n     *\n     * @return void\n     */\n    public function up()\n    {\n        Schema::create('users', function (Blueprint $table) {\n            $table->increments('id');\n            $table->string('username', 128)->nullable()->index();\n            $table->string('email', 255)->index();\n            $table->string('password', 255);\n            $table->string('first_name', 45)->nullable()->index();\n            $table->string('last_name', 45)->index();\n            $table->string('timezone', 45)->default('America/New_York');\n            $table->unsignedInteger('location_id');\n            $table->softDeletes();\n            $table->string('remember_token', 255)->nullable();\n            $table->timestamps();\n            $table->foreign('location_id', 'users_location_id_foreign')->references('id')->on('locations')->onUpdate('cascade')->onDelete('cascade');\n        });\n    }\n\n    /**\n     * Reverse the migrations.\n     *\n     * @return void\n     */\n    public function down()\n    {\n        Schema::dropIfExists('users');\n    }\n}\n```\n\n\n# Currently Supported DBMS's\nThese DBMS's are what are currently supported for creating migrations **from**. Migrations created will, as usual, follow what [database drivers Laravel migrations allow for](https://laravel.com/docs/8.x/database#introduction)\n\n- [x] MySQL\n- [ ] Postgres\n- [ ] SQLite\n- [ ] SQL Server\n"
  },
  {
    "path": "UPGRADE.md",
    "content": "## Upgrade from 3.* to 4.0\n\n### Foreign Key Sorting\nNew foreign key dependency sorting options, available as an env variable to potentially not sort by foreign key dependencies if not necessary.\nUpdate your `config/laravel-migration-generator.php` to have a new `sort_mode` key:\n\n```dotenv\n'sort_mode' => env('LMG_SORT_MODE', 'foreign_key'),\n```\n\n### New Stubs\nNew stubs for a `create` and a `modify` version for tables.\nIf you want to change how a `Schema::create` or `Schema::table` is output as, create a new `table-create.stub` or `table-modify.stub` and their driver variants as well if desired.\n\n### New Table and View Naming Tokens\n\nThree new tokens were added for the table stubs: `[Index]`, `[IndexedEmptyTimestamp]`, and `[IndexedTimestamp]`.\nFor use with foreign key / sorting in general to enforce a final sorting.\n\n`[Index]` is the numeric key (0,1,2,...) that the migration holds in the sort order.\n\n`[IndexedEmptyTimestamp]` is the `[Index]` but prefixed with the necessary digits and underscores for the file to be recognized as a migration. `0000_00_00_000001_migration.php`\n\n`[IndexedTimestamp]` is the current time incremented by `[Index]` seconds. So first migration would be the current time, second migration would be +1 second, third +2 seconds, etc.\n\n### New Table Stub Tokens\nTwo new tokens were added for table stubs: `[TableUp]` and `[TableDown]`.\nSee latest `stubs/table.stub`. It is suggested to upgrade all of your stubs using the latest stubs available by `vendor:publish --force`\n\n## Upgrade from 2.2.* to 3.0.0\n\n`skippable_tables` is now a comma delimited string instead of an array so they are compatible with .env files.\n\nAll config options have been moved to equivalent .env variables. Please update `config/laravel-migration-generator.php` with a `vendor:publish --force`.\nThe new environment variables are below:\n\n| Key | Default Value | Allowed Values | Description |\n| --- | ------------- | -------------- | ----------- |\n| LMG_RUN_AFTER_MIGRATIONS | false | boolean | Whether or not the migration generator should run after migrations have completed. |\n| LMG_CLEAR_OUTPUT_PATH | false | boolean | Whether or not to clear out the output path before creating new files |\n| LMG_TABLE_NAMING_SCHEME | [Timestamp]_create_[TableName]_table.php | string | The string to be used to name table migration files |\n| LMG_VIEW_NAMING_SCHEME | [Timestamp]_create_[ViewName]_view.php | string | The string to be used to name view migration files |\n| LMG_OUTPUT_PATH | tests/database/migrations | string | The path (relative to the root of your project) to where the files will be output to |\n| LMG_SKIPPABLE_TABLES | migrations | comma delimited string | The tables to be skipped |\n| LMG_PREFER_UNSIGNED_PREFIX | true | boolean | When true, uses `unsigned` variant methods instead of the `->unsigned()` modifier. |\n| LMG_USE_DEFINED_INDEX_NAMES | true | boolean | When true, uses index names defined by the database as the name parameter for index methods |\n| LMG_USE_DEFINED_FOREIGN_KEY_INDEX_NAMES | true | boolean | When true, uses foreign key index names defined by the database as the name parameter for foreign key methods |\n| LMG_USE_DEFINED_UNIQUE_KEY_INDEX_NAMES | true | boolean | When true, uses unique key index names defined by the database as the name parameter for the `unique` methods |\n| LMG_USE_DEFINED_PRIMARY_KEY_INDEX_NAMES | true | boolean | When true, uses primary key index name defined by the database as the name parameter for the `primary` method |\n| LMG_MYSQL_TABLE_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_TABLE_NAMING_SCHEME when the database driver is `mysql`. |\n| LMG_MYSQL_VIEW_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_VIEW_NAMING_SCHEME when the database driver is `mysql`. |\n| LMG_MYSQL_OUTPUT_PATH | null | ?boolean | When not null, this setting will override LMG_OUTPUT_PATH when the database driver is `mysql`. |\n| LMG_MYSQL_SKIPPABLE_TABLES | null | ?boolean | When not null, this setting will override LMG_SKIPPABLE_TABLES when the database driver is `mysql`. |\n| LMG_SQLITE_TABLE_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_TABLE_NAMING_SCHEME when the database driver is `sqlite`. |\n| LMG_SQLITE_VIEW_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_VIEW_NAMING_SCHEME when the database driver is `sqlite`. |\n| LMG_SQLITE_OUTPUT_PATH | null | ?boolean | When not null, this setting will override LMG_OUTPUT_PATH when the database driver is `sqlite`. |\n| LMG_SQLITE_SKIPPABLE_TABLES | null | ?boolean | When not null, this setting will override LMG_SKIPPABLE_TABLES when the database driver is `sqlite`. |\n| LMG_PGSQL_TABLE_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_TABLE_NAMING_SCHEME when the database driver is `pgsql`. |\n| LMG_PGSQL_VIEW_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_VIEW_NAMING_SCHEME when the database driver is `pgsql`. |\n| LMG_PGSQL_OUTPUT_PATH | null | ?boolean | When not null, this setting will override LMG_OUTPUT_PATH when the database driver is `pgsql`. |\n| LMG_PGSQL_SKIPPABLE_TABLES | null | ?boolean | When not null, this setting will override LMG_SKIPPABLE_TABLES when the database driver is `pgsql`. |\n| LMG_SQLSRV_TABLE_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_TABLE_NAMING_SCHEME when the database driver is `sqlsrc`. |\n| LMG_SQLSRV_VIEW_NAMING_SCHEME | null | ?boolean | When not null, this setting will override LMG_VIEW_NAMING_SCHEME when the database driver is `sqlsrv`. |\n| LMG_SQLSRV_OUTPUT_PATH | null | ?boolean | When not null, this setting will override LMG_OUTPUT_PATH when the database driver is `sqlsrv`. |\n| LMG_SQLSRV_SKIPPABLE_TABLES | null | ?boolean | When not null, this setting will override LMG_SKIPPABLE_TABLES when the database driver is `sqlsrv`. |\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"bennett-treptow/laravel-migration-generator\",\n    \"description\": \"Generate migrations from existing database structures\",\n    \"minimum-stability\": \"stable\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Bennett Treptow\",\n            \"email\": \"me@btreptow.com\"\n        }\n    ],\n    \"require\": {\n        \"php\": \"^7.4|^8.0|^8.1|^8.2|^8.3|^8.4\",\n        \"illuminate/support\": \"^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0\",\n        \"illuminate/console\": \"^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0\",\n        \"illuminate/database\": \"^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0\",\n        \"illuminate/config\": \"^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0\",\n        \"marcj/topsort\": \"^2.0\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"LaravelMigrationGenerator\\\\\": \"src/\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"Tests\\\\\": \"tests/\"\n        }\n    },\n    \"require-dev\": {\n        \"orchestra/testbench\": \"^6.17|^8.0|^9.0|^10.0\",\n        \"laravel/pint\": \"^1.15\"\n    },\n    \"scripts\": {\n        \"post-autoload-dump\": [\n            \"@php ./vendor/bin/testbench package:discover --ansi\"\n        ],\n        \"test\": [\n            \"vendor/bin/phpunit\"\n        ],\n        \"lint\": \"vendor/bin/pint\"\n    },\n    \"extra\": {\n        \"laravel\": {\n            \"providers\": [\n                \"LaravelMigrationGenerator\\\\LaravelMigrationGeneratorProvider\"\n            ]\n        }\n    },\n    \"prefer-stable\": true,\n    \"config\": {\n        \"audit\": {\n          \"block-insecure\": false\n        }\n    }\n}\n"
  },
  {
    "path": "config/laravel-migration-generator.php",
    "content": "<?php\n\nreturn [\n    'run_after_migrations' => env('LMG_RUN_AFTER_MIGRATIONS', false),\n    'clear_output_path' => env('LMG_CLEAR_OUTPUT_PATH', false),\n    //default configs\n    'table_naming_scheme' => env('LMG_TABLE_NAMING_SCHEME', '[IndexedTimestamp]_create_[TableName]_table.php'),\n    'view_naming_scheme' => env('LMG_VIEW_NAMING_SCHEME', '[IndexedTimestamp]_create_[ViewName]_view.php'),\n    'path' => env('LMG_OUTPUT_PATH', 'tests/database/migrations'),\n    'skippable_tables' => env('LMG_SKIPPABLE_TABLES', 'migrations'),\n    'skip_views' => env('LMG_SKIP_VIEWS', false),\n    'skippable_views' => env('LMG_SKIPPABLE_VIEWS', ''),\n    'sort_mode' => env('LMG_SORT_MODE', 'foreign_key'),\n    'definitions' => [\n        'prefer_unsigned_prefix' => env('LMG_PREFER_UNSIGNED_PREFIX', true),\n        'use_defined_index_names' => env('LMG_USE_DEFINED_INDEX_NAMES', true),\n        'use_defined_foreign_key_index_names' => env('LMG_USE_DEFINED_FOREIGN_KEY_INDEX_NAMES', true),\n        'use_defined_unique_key_index_names' => env('LMG_USE_DEFINED_UNIQUE_KEY_INDEX_NAMES', true),\n        'use_defined_primary_key_index_names' => env('LMG_USE_DEFINED_PRIMARY_KEY_INDEX_NAMES', true),\n        'with_comments' => env('LMG_WITH_COMMENTS', true),\n        'use_defined_datatype_on_timestamp' => env('LMG_USE_DEFINED_DATATYPE_ON_TIMESTAMP', false),\n    ],\n\n    //now driver specific configs\n    //null = use default\n    'mysql' => [\n        'table_naming_scheme' => env('LMG_MYSQL_TABLE_NAMING_SCHEME', null),\n        'view_naming_scheme' => env('LMG_MYSQL_VIEW_NAMING_SCHEME', null),\n        'path' => env('LMG_MYSQL_OUTPUT_PATH', null),\n        'skippable_tables' => env('LMG_MYSQL_SKIPPABLE_TABLES', null),\n        'skippable_views' => env('LMG_MYSQL_SKIPPABLE_VIEWS', null),\n    ],\n    'sqlite' => [\n        'table_naming_scheme' => env('LMG_SQLITE_TABLE_NAMING_SCHEME', null),\n        'view_naming_scheme' => env('LMG_SQLITE_VIEW_NAMING_SCHEME', null),\n        'path' => env('LMG_SQLITE_OUTPUT_PATH', null),\n        'skippable_tables' => env('LMG_SQLITE_SKIPPABLE_TABLES', null),\n        'skippable_views' => env('LMG_SQLITE_SKIPPABLE_VIEWS', null),\n    ],\n    'pgsql' => [\n        'table_naming_scheme' => env('LMG_PGSQL_TABLE_NAMING_SCHEME', null),\n        'view_naming_scheme' => env('LMG_PGSQL_VIEW_NAMING_SCHEME', null),\n        'path' => env('LMG_PGSQL_OUTPUT_PATH', null),\n        'skippable_tables' => env('LMG_PGSQL_SKIPPABLE_TABLES', null),\n        'skippable_views' => env('LMG_PGSQL_SKIPPABLE_VIEWS', null),\n    ],\n    'sqlsrv' => [\n        'table_naming_scheme' => env('LMG_SQLSRV_TABLE_NAMING_SCHEME', null),\n        'view_naming_scheme' => env('LMG_SQLSRV_VIEW_NAMING_SCHEME', null),\n        'path' => env('LMG_SQLSRV_OUTPUT_PATH', null),\n        'skippable_tables' => env('LMG_SQLSRV_SKIPPABLE_TABLES', null),\n        'skippable_views' => env('LMG_SQLSRV_SKIPPABLE_VIEWS', null),\n    ],\n];\n"
  },
  {
    "path": "docs/_config.yml",
    "content": "remote_theme: pmarsceill/just-the-docs\ncompress_html:\n  ignore:\n    envs: all\naux_links:\n  \"View on GitHub\":\n    - \"//github.com/bennett-treptow/laravel-migration-generator\"\n"
  },
  {
    "path": "docs/command.md",
    "content": "---\nlayout: default\ntitle: Running the Generator\nnav_order: 3\n---"
  },
  {
    "path": "docs/config.md",
    "content": "---\nlayout: default\ntitle: Config\nnav_order: 1\n---"
  },
  {
    "path": "docs/index.md",
    "content": "---\nlayout: default\ntitle: Laravel Migration Generator\nnav_order: 0\n---\n# Laravel Migration Generator\n\nGenerate migrations from existing database structures, an alternative to the schema dump provided by Laravel. This package will connect to your database and introspect the schema and generate migration files with columns and indices like they would be if they had originally come from a migration.\n\n## Quick Start\n```bash\ncomposer require --dev bennett-treptow/laravel-migration-generator\nphp artisan vendor:publish --provider=\"LaravelMigrationGenerator\\LaravelMigrationGeneratorProvider\"\n```\n\nLearn more about [config options](config.md) and [stubs](stubs.md)."
  },
  {
    "path": "docs/stubs.md",
    "content": "---\nlayout: default\ntitle: Stubs\nnav_order: 2\n---"
  },
  {
    "path": "phpunit.xml",
    "content": "<phpunit>\n    <testsuites>\n        <testsuite name=\"Unit\">\n            <directory suffix=\"Test.php\">./tests/Unit</directory>\n        </testsuite>\n    </testsuites>\n</phpunit>"
  },
  {
    "path": "pint.json",
    "content": "{\n  \"preset\": \"laravel\"\n}"
  },
  {
    "path": "src/Commands/GenerateMigrationsCommand.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Commands;\n\nuse Illuminate\\Console\\Command;\nuse Illuminate\\Support\\Arr;\nuse Illuminate\\Support\\Facades\\Config;\nuse Illuminate\\Support\\Facades\\DB;\nuse LaravelMigrationGenerator\\GeneratorManagers\\Interfaces\\GeneratorManagerInterface;\nuse LaravelMigrationGenerator\\GeneratorManagers\\MySQLGeneratorManager;\nuse LaravelMigrationGenerator\\Helpers\\ConfigResolver;\n\nclass GenerateMigrationsCommand extends Command\n{\n    protected $signature = 'generate:migrations {--path=default : The path where migrations will be output to} {--table=* : Only generate output for specified tables} {--view=* : Only generate output for specified views} {--connection=default : Use a different database connection specified in database config} {--empty-path : Clear other files in path, eg if wanting to replace all migrations}';\n\n    protected $description = 'Generate migrations from an existing database';\n\n    public function getConnection()\n    {\n        $connection = $this->option('connection');\n\n        if ($connection === 'default') {\n            $connection = Config::get('database.default');\n        }\n\n        if (! Config::has('database.connections.'.$connection)) {\n            throw new \\Exception('Could not find connection `'.$connection.'` in your config.');\n        }\n\n        return $connection;\n    }\n\n    public function getPath($driver)\n    {\n        $basePath = $this->option('path');\n        if ($basePath === 'default') {\n            $basePath = ConfigResolver::path($driver);\n        }\n\n        return $basePath;\n    }\n\n    public function handle()\n    {\n        try {\n            $connection = $this->getConnection();\n        } catch (\\Exception $e) {\n            $this->error($e->getMessage());\n\n            return 1;\n        }\n\n        $this->info('Using connection '.$connection);\n        DB::setDefaultConnection($connection);\n\n        $driver = Config::get('database.connections.'.$connection)['driver'];\n\n        $manager = $this->resolveGeneratorManager($driver);\n        if ($manager === false) {\n            $this->error('The `'.$driver.'` driver is not supported at this time.');\n\n            return 1;\n        }\n\n        $basePath = base_path($this->getPath($driver));\n\n        if ($this->option('empty-path') || config('laravel-migration-generator.clear_output_path')) {\n            foreach (glob($basePath.'/*.php') as $file) {\n                unlink($file);\n            }\n        }\n\n        $this->info('Using '.$basePath.' as the output path..');\n\n        $tableNames = Arr::wrap($this->option('table'));\n\n        $viewNames = Arr::wrap($this->option('view'));\n\n        $manager->handle($basePath, $tableNames, $viewNames);\n    }\n\n    /**\n     * @return false|GeneratorManagerInterface\n     */\n    protected function resolveGeneratorManager(string $driver)\n    {\n        $supported = [\n            'mysql' => MySQLGeneratorManager::class,\n        ];\n\n        if (! isset($supported[$driver])) {\n            return false;\n        }\n\n        return new $supported[$driver]();\n    }\n}\n"
  },
  {
    "path": "src/Definitions/ColumnDefinition.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Definitions;\n\nuse Illuminate\\Support\\Str;\nuse LaravelMigrationGenerator\\Helpers\\ValueToString;\nuse LaravelMigrationGenerator\\Helpers\\WritableTrait;\n\n/**\n * Class ColumnDefinition\n */\nclass ColumnDefinition\n{\n    use WritableTrait;\n\n    protected string $methodName = '';\n\n    protected array $methodParameters = [];\n\n    protected ?string $columnName = null;\n\n    protected bool $unsigned = false;\n\n    protected ?bool $nullable = null;\n\n    protected $defaultValue;\n\n    protected ?string $comment = null;\n\n    protected ?string $characterSet = null;\n\n    protected ?string $collation = null;\n\n    protected bool $autoIncrementing = false;\n\n    protected bool $index = false;\n\n    protected bool $primary = false;\n\n    protected bool $unique = false;\n\n    protected bool $useCurrent = false;\n\n    protected bool $useCurrentOnUpdate = false;\n\n    protected ?string $storedAs = null;\n\n    protected ?string $virtualAs = null;\n\n    protected bool $isUUID = false;\n\n    /** @var IndexDefinition[] */\n    protected array $indexDefinitions = [];\n\n    public function __construct($attributes = [])\n    {\n        foreach ($attributes as $attribute => $value) {\n            if (property_exists($this, $attribute)) {\n                $this->$attribute = $value;\n            }\n        }\n    }\n\n    //region Getters\n\n    public function getMethodName(): string\n    {\n        return $this->methodName;\n    }\n\n    public function getMethodParameters(): array\n    {\n        return $this->methodParameters;\n    }\n\n    public function getColumnName(): ?string\n    {\n        return $this->columnName;\n    }\n\n    public function isUnsigned(): bool\n    {\n        return $this->unsigned;\n    }\n\n    /**\n     * @return ?bool\n     */\n    public function isNullable(): ?bool\n    {\n        return $this->nullable;\n    }\n\n    /**\n     * @return mixed\n     */\n    public function getDefaultValue()\n    {\n        if (ValueToString::isCastedValue($this->defaultValue)) {\n            return ValueToString::parseCastedValue($this->defaultValue);\n        }\n\n        return $this->defaultValue;\n    }\n\n    public function getComment(): ?string\n    {\n        return $this->comment;\n    }\n\n    public function getCharacterSet(): ?string\n    {\n        return $this->characterSet;\n    }\n\n    public function getCollation(): ?string\n    {\n        return $this->collation;\n    }\n\n    public function isAutoIncrementing(): bool\n    {\n        return $this->autoIncrementing;\n    }\n\n    public function isIndex(): bool\n    {\n        return $this->index;\n    }\n\n    public function isPrimary(): bool\n    {\n        return $this->primary;\n    }\n\n    public function isUnique(): bool\n    {\n        return $this->unique;\n    }\n\n    public function useCurrent(): bool\n    {\n        return $this->useCurrent;\n    }\n\n    public function useCurrentOnUpdate(): bool\n    {\n        return $this->useCurrentOnUpdate;\n    }\n\n    public function getStoredAs(): ?string\n    {\n        return $this->storedAs;\n    }\n\n    public function getVirtualAs(): ?string\n    {\n        return $this->virtualAs;\n    }\n\n    public function isUUID(): bool\n    {\n        return $this->isUUID;\n    }\n\n    //endregion\n\n    //region Setters\n\n    public function setMethodName(string $methodName): ColumnDefinition\n    {\n        $this->methodName = $methodName;\n\n        return $this;\n    }\n\n    public function setMethodParameters(array $methodParameters): ColumnDefinition\n    {\n        $this->methodParameters = $methodParameters;\n\n        return $this;\n    }\n\n    public function setColumnName(?string $columnName): ColumnDefinition\n    {\n        $this->columnName = $columnName;\n\n        return $this;\n    }\n\n    public function setUnsigned(bool $unsigned): ColumnDefinition\n    {\n        $this->unsigned = $unsigned;\n\n        return $this;\n    }\n\n    /**\n     * @param  ?bool  $nullable\n     */\n    public function setNullable(?bool $nullable): ColumnDefinition\n    {\n        $this->nullable = $nullable;\n\n        return $this;\n    }\n\n    /**\n     * @param  mixed  $defaultValue\n     * @return ColumnDefinition\n     */\n    public function setDefaultValue($defaultValue)\n    {\n        $this->defaultValue = $defaultValue;\n\n        return $this;\n    }\n\n    public function setComment(?string $comment): ColumnDefinition\n    {\n        $this->comment = $comment;\n\n        return $this;\n    }\n\n    /**\n     * @param  string|null  $collation\n     */\n    public function setCharacterSet(?string $characterSet): ColumnDefinition\n    {\n        $this->characterSet = $characterSet;\n\n        return $this;\n    }\n\n    public function setCollation(?string $collation): ColumnDefinition\n    {\n        $this->collation = $collation;\n\n        return $this;\n    }\n\n    public function setAutoIncrementing(bool $autoIncrementing): ColumnDefinition\n    {\n        $this->autoIncrementing = $autoIncrementing;\n\n        return $this;\n    }\n\n    public function setStoredAs(?string $storedAs): ColumnDefinition\n    {\n        $this->storedAs = $storedAs;\n\n        return $this;\n    }\n\n    public function setVirtualAs(?string $virtualAs): ColumnDefinition\n    {\n        $this->virtualAs = $virtualAs;\n\n        return $this;\n    }\n\n    public function addIndexDefinition(IndexDefinition $definition): ColumnDefinition\n    {\n        $this->indexDefinitions[] = $definition;\n\n        return $this;\n    }\n\n    public function setIndex(bool $index): ColumnDefinition\n    {\n        $this->index = $index;\n\n        return $this;\n    }\n\n    public function setPrimary(bool $primary): ColumnDefinition\n    {\n        $this->primary = $primary;\n\n        return $this;\n    }\n\n    public function setUnique(bool $unique): ColumnDefinition\n    {\n        $this->unique = $unique;\n\n        return $this;\n    }\n\n    public function setUseCurrent(bool $useCurrent): ColumnDefinition\n    {\n        $this->useCurrent = $useCurrent;\n\n        return $this;\n    }\n\n    public function setUseCurrentOnUpdate(bool $useCurrentOnUpdate): ColumnDefinition\n    {\n        $this->useCurrentOnUpdate = $useCurrentOnUpdate;\n\n        return $this;\n    }\n\n    public function setIsUUID(bool $isUUID): ColumnDefinition\n    {\n        $this->isUUID = $isUUID;\n\n        return $this;\n    }\n\n    //endregion\n\n    protected function isNullableMethod($methodName)\n    {\n        return ! in_array($methodName, ['softDeletes', 'morphs', 'nullableMorphs', 'rememberToken', 'nullableUuidMorphs']) && ! $this->isPrimaryKeyMethod($methodName);\n    }\n\n    protected function isPrimaryKeyMethod($methodName)\n    {\n        return in_array($methodName, ['tinyIncrements', 'mediumIncrements', 'increments', 'bigIncrements', 'id']);\n    }\n\n    protected function canBeUnsigned($methodName)\n    {\n        return ! in_array($methodName, ['morphs', 'nullableMorphs']) && ! $this->isPrimaryKeyMethod($methodName);\n    }\n\n    protected function guessLaravelMethod()\n    {\n        if ($this->primary && $this->unsigned && $this->autoIncrementing) {\n            //some sort of increments field\n            if ($this->methodName === 'bigInteger') {\n                if ($this->columnName === 'id') {\n                    return [null, 'id', []];\n                } else {\n                    return [$this->columnName, 'bigIncrements', []];\n                }\n            } elseif ($this->methodName === 'mediumInteger') {\n                return [$this->columnName, 'mediumIncrements', []];\n            } elseif ($this->methodName === 'integer') {\n                return [$this->columnName, 'increments', []];\n            } elseif ($this->methodName === 'smallInteger') {\n                return [$this->columnName, 'smallIncrements', []];\n            } elseif ($this->methodName === 'tinyInteger') {\n                return [$this->columnName, 'tinyIncrements', []];\n            }\n        }\n\n        if ($this->methodName === 'tinyInteger' && ! $this->unsigned) {\n            $boolean = false;\n            if (in_array($this->defaultValue, ['true', 'false', true, false, 'TRUE', 'FALSE', '1', '0', 1, 0], true)) {\n                $boolean = true;\n            }\n            if (Str::startsWith(strtoupper($this->columnName), ['IS_', 'HAS_'])) {\n                $boolean = true;\n            }\n            if ($boolean) {\n                return [$this->columnName, 'boolean', []];\n            }\n        }\n\n        if ($this->methodName === 'morphs' && $this->nullable === true) {\n            return [$this->columnName, 'nullableMorphs', []];\n        }\n\n        if ($this->methodName === 'uuidMorphs' && $this->nullable === true) {\n            return [$this->columnName, 'nullableUuidMorphs', []];\n        }\n\n        if ($this->methodName === 'string' && $this->columnName === 'remember_token' && $this->nullable === true) {\n            return [null, 'rememberToken', []];\n        }\n        if ($this->isUUID() && $this->methodName !== 'uuidMorphs') {\n            //only override if not already uuidMorphs\n            return [$this->columnName, 'uuid', []];\n        }\n\n        if (config('laravel-migration-generator.definitions.prefer_unsigned_prefix') && $this->unsigned) {\n            $availableUnsignedPrefixes = [\n                'bigInteger',\n                'decimal',\n                'integer',\n                'mediumInteger',\n                'smallInteger',\n                'tinyInteger',\n            ];\n            if (in_array($this->methodName, $availableUnsignedPrefixes)) {\n                return [$this->columnName, 'unsigned'.ucfirst($this->methodName), $this->methodParameters];\n            }\n        }\n\n        return [$this->columnName, $this->methodName, $this->methodParameters];\n    }\n\n    public function render(): string\n    {\n        [$finalColumnName, $finalMethodName, $finalMethodParameters] = $this->guessLaravelMethod();\n\n        $initialString = '$table->'.$finalMethodName.'(';\n        if ($finalColumnName !== null) {\n            $initialString .= ValueToString::make($finalColumnName);\n        }\n        if (count($finalMethodParameters) > 0) {\n            foreach ($finalMethodParameters as $param) {\n                $initialString .= ', '.ValueToString::make($param);\n            }\n        }\n        $initialString .= ')';\n        if ($this->unsigned && $this->canBeUnsigned($finalMethodName) && ! Str::startsWith($finalMethodName, 'unsigned')) {\n            $initialString .= '->unsigned()';\n        }\n\n        if ($this->defaultValue === 'NULL') {\n            $this->defaultValue = null;\n            $this->nullable = true;\n        }\n\n        if ($this->isNullableMethod($finalMethodName)) {\n            if ($this->nullable === true) {\n                $initialString .= '->nullable()';\n            }\n        }\n\n        if ($this->defaultValue !== null) {\n            $initialString .= '->default(';\n            $initialString .= ValueToString::make($this->defaultValue, false);\n            $initialString .= ')';\n        }\n        if ($this->useCurrent) {\n            $initialString .= '->useCurrent()';\n        }\n        if ($this->useCurrentOnUpdate) {\n            $initialString .= '->useCurrentOnUpdate()';\n        }\n\n        if ($this->index) {\n            $indexName = '';\n            if (count($this->indexDefinitions) === 1 && config('laravel-migration-generator.definitions.use_defined_index_names')) {\n                $indexName = ValueToString::make($this->indexDefinitions[0]->getIndexName());\n            }\n            $initialString .= '->index('.$indexName.')';\n        }\n\n        if ($this->primary && ! $this->isPrimaryKeyMethod($finalMethodName)) {\n            $indexName = '';\n            if (count($this->indexDefinitions) === 1 && config('laravel-migration-generator.definitions.use_defined_primary_key_index_names')) {\n                if ($this->indexDefinitions[0]->getIndexName() !== null) {\n                    $indexName = ValueToString::make($this->indexDefinitions[0]->getIndexName());\n                }\n            }\n            $initialString .= '->primary('.$indexName.')';\n        }\n\n        if ($this->unique) {\n            $indexName = '';\n            if (count($this->indexDefinitions) === 1 && config('laravel-migration-generator.definitions.use_defined_unique_key_index_names')) {\n                $indexName = ValueToString::make($this->indexDefinitions[0]->getIndexName());\n            }\n            $initialString .= '->unique('.$indexName.')';\n        }\n\n        if ($this->storedAs !== null) {\n            $initialString .= '->storedAs('.ValueToString::make($this->storedAs, false, false).')';\n        }\n\n        if ($this->virtualAs !== null) {\n            $initialString .= '->virtualAs('.ValueToString::make($this->virtualAs, false, false).')';\n        }\n\n        if ($this->comment !== null && config('laravel-migration-generator.definitions.with_comments')) {\n            $initialString .= '->comment('.ValueToString::make($this->comment, false, false).')';\n        }\n\n        return $initialString;\n    }\n}\n"
  },
  {
    "path": "src/Definitions/IndexDefinition.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Definitions;\n\nuse LaravelMigrationGenerator\\Helpers\\ValueToString;\nuse LaravelMigrationGenerator\\Helpers\\WritableTrait;\n\nclass IndexDefinition\n{\n    use WritableTrait;\n\n    public const TYPE_FOREIGN = 'foreign';\n\n    protected string $indexType = '';\n\n    protected ?string $indexName = null; //primary keys usually don't have a name\n\n    protected array $indexColumns = [];\n\n    protected array $foreignReferencedColumns = [];\n\n    protected string $foreignReferencedTable = '';\n\n    protected array $constraintActions = [];\n\n    public function __construct($attributes = [])\n    {\n        foreach ($attributes as $attribute => $value) {\n            if (property_exists($this, $attribute)) {\n                $this->$attribute = $value;\n            }\n        }\n    }\n\n    //region Getters\n\n    public function getIndexType(): string\n    {\n        return $this->indexType;\n    }\n\n    public function getIndexName(): ?string\n    {\n        return $this->indexName;\n    }\n\n    public function getIndexColumns(): array\n    {\n        return $this->indexColumns;\n    }\n\n    public function getForeignReferencedColumns(): array\n    {\n        return $this->foreignReferencedColumns;\n    }\n\n    public function getForeignReferencedTable(): string\n    {\n        return $this->foreignReferencedTable;\n    }\n\n    public function getConstraintActions(): array\n    {\n        return $this->constraintActions;\n    }\n\n    //endregion\n    //region Setters\n\n    public function setIndexType(string $indexType): IndexDefinition\n    {\n        $this->indexType = $indexType;\n\n        return $this;\n    }\n\n    public function setIndexName(string $indexName): IndexDefinition\n    {\n        $this->indexName = $indexName;\n\n        return $this;\n    }\n\n    public function setIndexColumns(array $indexColumns): IndexDefinition\n    {\n        $this->indexColumns = $indexColumns;\n\n        return $this;\n    }\n\n    public function setForeignReferencedColumns(array $foreignReferencedColumns): IndexDefinition\n    {\n        $this->foreignReferencedColumns = $foreignReferencedColumns;\n\n        return $this;\n    }\n\n    public function setForeignReferencedTable(string $foreignReferencedTable): IndexDefinition\n    {\n        $this->foreignReferencedTable = $foreignReferencedTable;\n\n        return $this;\n    }\n\n    public function setConstraintActions(array $constraintActions): IndexDefinition\n    {\n        $this->constraintActions = $constraintActions;\n\n        return $this;\n    }\n\n    //endregion\n\n    public function isMultiColumnIndex()\n    {\n        return count($this->indexColumns) > 1;\n    }\n\n    /**\n     * Get the escaped index name for safe inclusion in generated PHP code.\n     */\n    protected function getEscapedIndexName(): string\n    {\n        return ValueToString::escape($this->getIndexName() ?? '');\n    }\n\n    public function render(): string\n    {\n        if ($this->indexType === 'foreign') {\n            $indexName = '';\n            if (config('laravel-migration-generator.definitions.use_defined_foreign_key_index_names')) {\n                $indexName = ', \\''.$this->getEscapedIndexName().'\\'';\n            }\n\n            $base = '$table->foreign('.ValueToString::make($this->indexColumns, true).$indexName.')->references('.ValueToString::make($this->foreignReferencedColumns, true).')->on('.ValueToString::make($this->foreignReferencedTable).')';\n            foreach ($this->constraintActions as $type => $action) {\n                $base .= '->on'.ucfirst($type).'('.ValueToString::make($action).')';\n            }\n\n            return $base;\n        } elseif ($this->indexType === 'primary') {\n            $indexName = '';\n            if (config('laravel-migration-generator.definitions.use_defined_primary_key_index_names') && $this->getIndexName() !== null) {\n                $indexName = ', \\''.$this->getEscapedIndexName().'\\'';\n            }\n\n            return '$table->primary('.ValueToString::make($this->indexColumns).$indexName.')';\n        } elseif ($this->indexType === 'unique') {\n            $indexName = '';\n            if (config('laravel-migration-generator.definitions.use_defined_unique_key_index_names')) {\n                $indexName = ', \\''.$this->getEscapedIndexName().'\\'';\n            }\n\n            return '$table->unique('.ValueToString::make($this->indexColumns).$indexName.')';\n        } elseif ($this->indexType === 'index') {\n            $indexName = '';\n            if (config('laravel-migration-generator.definitions.use_defined_index_names')) {\n                $indexName = ', \\''.$this->getEscapedIndexName().'\\'';\n            }\n\n            return '$table->index('.ValueToString::make($this->indexColumns).$indexName.')';\n        } elseif ($this->indexType === 'fulltext') {\n            $indexName = '';\n            if (config('laravel-migration-generator.definitions.use_defined_index_names')) {\n                $indexName = ', \\''.$this->getEscapedIndexName().'\\'';\n            }\n\n            return '$table->fullText('.ValueToString::make($this->indexColumns).$indexName.')';\n        } elseif ($this->indexType === 'spatial') {\n            $indexName = '';\n            if (config('laravel-migration-generator.definitions.use_defined_index_names')) {\n                $indexName = ', \\''.$this->getEscapedIndexName().'\\'';\n            }\n\n            return '$table->spatialIndex('.ValueToString::make($this->indexColumns).$indexName.')';\n        }\n\n        return '';\n    }\n}\n"
  },
  {
    "path": "src/Definitions/TableDefinition.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Definitions;\n\nuse LaravelMigrationGenerator\\Formatters\\TableFormatter;\n\nclass TableDefinition\n{\n    protected string $tableName = '';\n\n    protected string $driver = '';\n\n    /** @var array<ColumnDefinition> */\n    protected array $columnDefinitions = [];\n\n    protected array $indexDefinitions = [];\n\n    public function __construct($attributes = [])\n    {\n        foreach ($attributes as $attribute => $value) {\n            if (property_exists($this, $attribute)) {\n                $this->$attribute = $value;\n            }\n        }\n    }\n\n    public function getDriver(): string\n    {\n        return $this->driver;\n    }\n\n    public function getPresentableTableName(): string\n    {\n        if (count($this->getColumnDefinitions()) === 0) {\n            if (count($definitions = $this->getIndexDefinitions()) > 0) {\n                $first = collect($definitions)->first();\n\n                //a fk only table from dependency resolution\n                return $this->getTableName().'_'.$first->getIndexName();\n            }\n        }\n\n        return $this->getTableName();\n    }\n\n    public function getTableName(): string\n    {\n        return $this->tableName;\n    }\n\n    public function setTableName(string $tableName)\n    {\n        $this->tableName = $tableName;\n\n        return $this;\n    }\n\n    public function getColumnDefinitions(): array\n    {\n        return $this->columnDefinitions;\n    }\n\n    public function setColumnDefinitions(array $columnDefinitions)\n    {\n        $this->columnDefinitions = $columnDefinitions;\n\n        return $this;\n    }\n\n    public function addColumnDefinition(ColumnDefinition $definition)\n    {\n        $this->columnDefinitions[] = $definition;\n\n        return $this;\n    }\n\n    /**\n     * @return array<IndexDefinition>\n     */\n    public function getIndexDefinitions(): array\n    {\n        return $this->indexDefinitions;\n    }\n\n    /** @return array<IndexDefinition> */\n    public function getForeignKeyDefinitions(): array\n    {\n        return collect($this->getIndexDefinitions())->filter(function ($indexDefinition) {\n            return $indexDefinition->getIndexType() == IndexDefinition::TYPE_FOREIGN;\n        })->toArray();\n    }\n\n    public function setIndexDefinitions(array $indexDefinitions)\n    {\n        $this->indexDefinitions = $indexDefinitions;\n\n        return $this;\n    }\n\n    public function addIndexDefinition(IndexDefinition $definition)\n    {\n        $this->indexDefinitions[] = $definition;\n\n        return $this;\n    }\n\n    public function removeIndexDefinition(IndexDefinition $definition)\n    {\n        foreach ($this->indexDefinitions as $key => $indexDefinition) {\n            if ($definition->getIndexName() == $indexDefinition->getIndexName()) {\n                unset($this->indexDefinitions[$key]);\n\n                break;\n            }\n        }\n\n        return $this;\n    }\n\n    public function getPrimaryKey(): array\n    {\n        return collect($this->getColumnDefinitions())\n            ->filter(function (ColumnDefinition $columnDefinition) {\n                return $columnDefinition->isPrimary();\n            })->toArray();\n    }\n\n    public function formatter(): TableFormatter\n    {\n        return new TableFormatter($this);\n    }\n}\n"
  },
  {
    "path": "src/Definitions/ViewDefinition.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Definitions;\n\nuse LaravelMigrationGenerator\\Formatters\\ViewFormatter;\n\nclass ViewDefinition\n{\n    protected string $driver = '';\n\n    protected string $viewName = '';\n\n    protected ?string $schema = null;\n\n    public function __construct($attributes = [])\n    {\n        foreach ($attributes as $attribute => $value) {\n            if (property_exists($this, $attribute)) {\n                $this->$attribute = $value;\n            }\n        }\n    }\n\n    public function getDriver(): string\n    {\n        return $this->driver;\n    }\n\n    public function setDriver(string $driver): ViewDefinition\n    {\n        $this->driver = $driver;\n\n        return $this;\n    }\n\n    public function getSchema(): string\n    {\n        return $this->schema;\n    }\n\n    public function setSchema(string $schema): ViewDefinition\n    {\n        $this->schema = $schema;\n\n        return $this;\n    }\n\n    public function getViewName(): string\n    {\n        return $this->viewName;\n    }\n\n    public function setViewName(string $viewName): ViewDefinition\n    {\n        $this->viewName = $viewName;\n\n        return $this;\n    }\n\n    public function formatter(): ViewFormatter\n    {\n        return new ViewFormatter($this);\n    }\n}\n"
  },
  {
    "path": "src/Formatters/TableFormatter.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Formatters;\n\nuse Illuminate\\Support\\Str;\nuse LaravelMigrationGenerator\\Definitions\\TableDefinition;\nuse LaravelMigrationGenerator\\Helpers\\ConfigResolver;\nuse LaravelMigrationGenerator\\Helpers\\Formatter;\n\nclass TableFormatter\n{\n    private TableDefinition $tableDefinition;\n\n    public function __construct(TableDefinition $tableDefinition)\n    {\n        $this->tableDefinition = $tableDefinition;\n    }\n\n    public function render($tabCharacter = '    ')\n    {\n        $tableName = $this->tableDefinition->getPresentableTableName();\n\n        $schema = $this->getSchema($tabCharacter);\n        $stub = file_get_contents($this->getStubPath());\n        if (strpos($stub, '[TableUp]') !== false) {\n            //uses new syntax\n            $stub = Formatter::replace($tabCharacter, '[TableUp]', $this->stubTableUp($tabCharacter), $stub);\n            $stub = Formatter::replace($tabCharacter, '[TableDown]', $this->stubTableDown($tabCharacter), $stub);\n        }\n\n        $stub = str_replace('[TableName:Studly]', Str::studly($tableName), $stub);\n        $stub = str_replace('[TableName]', $tableName, $stub);\n        $stub = Formatter::replace($tabCharacter, '[Schema]', $schema, $stub);\n\n        return $stub;\n    }\n\n    public function getStubFileName($index = 0): string\n    {\n        $driver = $this->tableDefinition->getDriver();\n        $baseStubFileName = ConfigResolver::tableNamingScheme($driver);\n        foreach ($this->stubNameVariables($index) as $variable => $replacement) {\n            if (preg_match(\"/\\[\".$variable.\"\\]/i\", $baseStubFileName) === 1) {\n                $baseStubFileName = preg_replace(\"/\\[\".$variable.\"\\]/i\", $replacement, $baseStubFileName);\n            }\n        }\n\n        return $baseStubFileName;\n    }\n\n    public function getStubPath(): string\n    {\n        $driver = $this->tableDefinition->getDriver();\n\n        if (file_exists($overridden = resource_path('stubs/vendor/laravel-migration-generator/'.$driver.'-table.stub'))) {\n            return $overridden;\n        }\n\n        if (file_exists($overridden = resource_path('stubs/vendor/laravel-migration-generator/table.stub'))) {\n            return $overridden;\n        }\n\n        return __DIR__.'/../../stubs/table.stub';\n    }\n\n    public function getStubCreatePath(): string\n    {\n        $driver = $this->tableDefinition->getDriver();\n\n        if (file_exists($overridden = resource_path('stubs/vendor/laravel-migration-generator/'.$driver.'-table-create.stub'))) {\n            return $overridden;\n        }\n\n        if (file_exists($overridden = resource_path('stubs/vendor/laravel-migration-generator/table-create.stub'))) {\n            return $overridden;\n        }\n\n        return __DIR__.'/../../stubs/table-create.stub';\n    }\n\n    public function getStubModifyPath(): string\n    {\n        $driver = $this->tableDefinition->getDriver();\n\n        if (file_exists($overridden = resource_path('stubs/vendor/laravel-migration-generator/'.$driver.'-table-modify.stub'))) {\n            return $overridden;\n        }\n\n        if (file_exists($overridden = resource_path('stubs/vendor/laravel-migration-generator/table-modify.stub'))) {\n            return $overridden;\n        }\n\n        return __DIR__.'/../../stubs/table-modify.stub';\n    }\n\n    public function stubNameVariables($index): array\n    {\n        $tableName = $this->tableDefinition->getPresentableTableName();\n\n        return [\n            'TableName:Studly' => Str::studly($tableName),\n            'TableName:Lowercase' => strtolower($tableName),\n            'TableName' => $tableName,\n            'Timestamp' => app('laravel-migration-generator:time')->format('Y_m_d_His'),\n            'Index' => (string) $index,\n            'IndexedEmptyTimestamp' => '0000_00_00_'.str_pad((string) $index, 6, '0', STR_PAD_LEFT),\n            'IndexedTimestamp' => app('laravel-migration-generator:time')->clone()->addSeconds($index)->format('Y_m_d_His'),\n        ];\n    }\n\n    public function getSchema($tab = ''): string\n    {\n        $formatter = new Formatter($tab);\n        collect($this->tableDefinition->getColumnDefinitions())\n            ->filter(fn ($col) => $col->isWritable())\n            ->each(function ($column) use ($formatter) {\n                $formatter->line($column->render().';');\n            });\n\n        $indices = collect($this->tableDefinition->getIndexDefinitions())\n            ->filter(fn ($index) => $index->isWritable());\n\n        if ($indices->count() > 0) {\n            if (count($this->tableDefinition->getColumnDefinitions()) > 0) {\n                $formatter->line('');\n            }\n            $indices->each(function ($index) use ($formatter) {\n                $formatter->line($index->render().';');\n            });\n        }\n\n        return $formatter->render();\n    }\n\n    public function stubTableUp($tab = '', $variables = null): string\n    {\n        if ($variables === null) {\n            $variables = $this->getStubVariables($tab);\n        }\n        if (count($this->tableDefinition->getColumnDefinitions()) === 0) {\n            $tableModifyStub = file_get_contents($this->getStubModifyPath());\n            foreach ($variables as $var => $replacement) {\n                $tableModifyStub = Formatter::replace($tab, '['.$var.']', $replacement, $tableModifyStub);\n            }\n\n            return $tableModifyStub;\n        }\n\n        $tableUpStub = file_get_contents($this->getStubCreatePath());\n        foreach ($variables as $var => $replacement) {\n            $tableUpStub = Formatter::replace($tab, '['.$var.']', $replacement, $tableUpStub);\n        }\n\n        return $tableUpStub;\n    }\n\n    public function stubTableDown($tab = ''): string\n    {\n        if (count($this->tableDefinition->getColumnDefinitions()) === 0) {\n            $schema = 'Schema::table(\\''.$this->tableDefinition->getTableName().'\\', function(Blueprint $table){'.\"\\n\";\n            foreach ($this->tableDefinition->getForeignKeyDefinitions() as $indexDefinition) {\n                $schema .= $tab.'$table->dropForeign(\\''.$indexDefinition->getIndexName().'\\');'.\"\\n\";\n            }\n\n            return $schema.'});';\n        }\n\n        return 'Schema::dropIfExists(\\''.$this->tableDefinition->getTableName().'\\');';\n    }\n\n    protected function getStubVariables($tab = '')\n    {\n        $tableName = $this->tableDefinition->getTableName();\n\n        return [\n            'TableName:Studly' => Str::studly($tableName),\n            'TableName:Lowercase' => strtolower($tableName),\n            'TableName' => $tableName,\n            'Schema' => $this->getSchema($tab),\n        ];\n    }\n\n    public function write(string $basePath, $index = 0, string $tabCharacter = '    '): string\n    {\n        $stub = $this->render($tabCharacter);\n\n        $fileName = $this->getStubFileName($index);\n        file_put_contents($final = $basePath.'/'.$fileName, $stub);\n\n        return $final;\n    }\n}\n"
  },
  {
    "path": "src/Formatters/ViewFormatter.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Formatters;\n\nuse Illuminate\\Support\\Str;\nuse LaravelMigrationGenerator\\Definitions\\ViewDefinition;\nuse LaravelMigrationGenerator\\Helpers\\ConfigResolver;\nuse LaravelMigrationGenerator\\Helpers\\Formatter;\n\nclass ViewFormatter\n{\n    private ViewDefinition $definition;\n\n    public function __construct(ViewDefinition $definition)\n    {\n        $this->definition = $definition;\n    }\n\n    public function stubNameVariables($index = 0)\n    {\n        return [\n            'ViewName:Studly' => Str::studly($viewName = $this->definition->getViewName()),\n            'ViewName:Lowercase' => strtolower($viewName),\n            'ViewName' => $viewName,\n            'Timestamp' => app('laravel-migration-generator:time')->format('Y_m_d_His'),\n            'Index' => '0000_00_00_'.str_pad((string) $index, 6, '0', STR_PAD_LEFT),\n            'IndexedEmptyTimestamp' => '0000_00_00_'.str_pad((string) $index, 6, '0', STR_PAD_LEFT),\n            'IndexedTimestamp' => app('laravel-migration-generator:time')->clone()->addSeconds($index)->format('Y_m_d_His'),\n        ];\n    }\n\n    protected function getStubFileName($index = 0)\n    {\n        $driver = $this->definition->getDriver();\n\n        $baseStubFileName = ConfigResolver::viewNamingScheme($driver);\n        foreach ($this->stubNameVariables($index) as $variable => $replacement) {\n            if (preg_match(\"/\\[\".$variable.\"\\]/i\", $baseStubFileName) === 1) {\n                $baseStubFileName = preg_replace(\"/\\[\".$variable.\"\\]/i\", $replacement, $baseStubFileName);\n            }\n        }\n\n        return $baseStubFileName;\n    }\n\n    protected function getStubPath()\n    {\n        $driver = $this->definition->getDriver();\n\n        if (file_exists($overridden = resource_path('stubs/vendor/laravel-migration-generator/'.$driver.'-view.stub'))) {\n            return $overridden;\n        }\n\n        if (file_exists($overridden = resource_path('stubs/vendor/laravel-migration-generator/view.stub'))) {\n            return $overridden;\n        }\n\n        return __DIR__.'/../../stubs/view.stub';\n    }\n\n    public function render($tabCharacter = '    ')\n    {\n        $schema = $this->definition->getSchema();\n        $stub = file_get_contents($this->getStubPath());\n        $variables = [\n            '[ViewName:Studly]' => Str::studly($viewName = $this->definition->getViewName()),\n            '[ViewName]' => $viewName,\n            '[Schema]' => $schema,\n        ];\n        foreach ($variables as $key => $value) {\n            $stub = Formatter::replace($tabCharacter, $key, $value, $stub);\n        }\n\n        return $stub;\n    }\n\n    public function write(string $basePath, $index = 0, string $tabCharacter = '    '): string\n    {\n        $stub = $this->render($tabCharacter);\n\n        $fileName = $this->getStubFileName($index);\n        file_put_contents($final = $basePath.'/'.$fileName, $stub);\n\n        return $final;\n    }\n}\n"
  },
  {
    "path": "src/GeneratorManagers/BaseGeneratorManager.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\GeneratorManagers;\n\nuse LaravelMigrationGenerator\\Definitions\\TableDefinition;\nuse LaravelMigrationGenerator\\Definitions\\ViewDefinition;\nuse LaravelMigrationGenerator\\GeneratorManagers\\Interfaces\\GeneratorManagerInterface;\nuse LaravelMigrationGenerator\\Helpers\\ConfigResolver;\nuse LaravelMigrationGenerator\\Helpers\\DependencyResolver;\n\nabstract class BaseGeneratorManager implements GeneratorManagerInterface\n{\n    protected array $tableDefinitions = [];\n\n    protected array $viewDefinitions = [];\n\n    abstract public function init();\n\n    public function createMissingDirectory($basePath)\n    {\n        if (! is_dir($basePath)) {\n            mkdir($basePath, 0777, true);\n        }\n    }\n\n    /**\n     * @return array<TableDefinition>\n     */\n    public function getTableDefinitions(): array\n    {\n        return $this->tableDefinitions;\n    }\n\n    /**\n     * @return array<ViewDefinition>\n     */\n    public function getViewDefinitions(): array\n    {\n        return $this->viewDefinitions;\n    }\n\n    public function addTableDefinition(TableDefinition $tableDefinition): BaseGeneratorManager\n    {\n        $this->tableDefinitions[] = $tableDefinition;\n\n        return $this;\n    }\n\n    public function addViewDefinition(ViewDefinition $definition): BaseGeneratorManager\n    {\n        $this->viewDefinitions[] = $definition;\n\n        return $this;\n    }\n\n    public function handle(string $basePath, array $tableNames = [], array $viewNames = [])\n    {\n        $this->init();\n\n        $tableDefinitions = collect($this->getTableDefinitions());\n        $viewDefinitions = collect($this->getViewDefinitions());\n\n        $this->createMissingDirectory($basePath);\n\n        if (count($tableNames) > 0) {\n            $tableDefinitions = $tableDefinitions->filter(function ($tableDefinition) use ($tableNames) {\n                return in_array($tableDefinition->getTableName(), $tableNames);\n            });\n        }\n        if (count($viewNames) > 0) {\n            $viewDefinitions = $viewDefinitions->filter(function ($viewGenerator) use ($viewNames) {\n                return in_array($viewGenerator->getViewName(), $viewNames);\n            });\n        }\n\n        $tableDefinitions = $tableDefinitions->filter(function ($tableDefinition) {\n            return ! $this->skipTable($tableDefinition->getTableName());\n        });\n\n        $viewDefinitions = $viewDefinitions->filter(function ($viewDefinition) {\n            return ! $this->skipView($viewDefinition->getViewName());\n        });\n\n        $sorted = $this->sortTables($tableDefinitions->toArray());\n\n        $this->writeTableMigrations($sorted, $basePath);\n\n        $this->writeViewMigrations($viewDefinitions->toArray(), $basePath, count($sorted));\n    }\n\n    /**\n     * @param  array<TableDefinition>  $tableDefinitions\n     * @return array<TableDefinition>\n     */\n    public function sortTables(array $tableDefinitions): array\n    {\n        if (count($tableDefinitions) <= 1) {\n            return $tableDefinitions;\n        }\n\n        if (config('laravel-migration-generator.sort_mode') == 'foreign_key') {\n            return (new DependencyResolver($tableDefinitions))->getDependencyOrder();\n        }\n\n        return $tableDefinitions;\n    }\n\n    /**\n     * @param  array<TableDefinition>  $tableDefinitions\n     */\n    public function writeTableMigrations(array $tableDefinitions, $basePath)\n    {\n        foreach ($tableDefinitions as $key => $tableDefinition) {\n            $tableDefinition->formatter()->write($basePath, $key);\n        }\n    }\n\n    /**\n     * @param  array<ViewDefinition>  $viewDefinitions\n     */\n    public function writeViewMigrations(array $viewDefinitions, $basePath, $tableCount = 0)\n    {\n        foreach ($viewDefinitions as $key => $view) {\n            $view->formatter()->write($basePath, $tableCount + $key);\n        }\n    }\n\n    /**\n     * @return array<string>\n     */\n    public function skippableTables(): array\n    {\n        return ConfigResolver::skippableTables(static::driver());\n    }\n\n    public function skipTable($table): bool\n    {\n        return in_array($table, $this->skippableTables());\n    }\n\n    /**\n     * @return array<string>\n     */\n    public function skippableViews(): array\n    {\n        return ConfigResolver::skippableViews(static::driver());\n    }\n\n    public function skipView($view): bool\n    {\n        $skipViews = config('laravel-migration-generator.skip_views');\n        if ($skipViews) {\n            return true;\n        }\n\n        return in_array($view, $this->skippableViews());\n    }\n}\n"
  },
  {
    "path": "src/GeneratorManagers/Interfaces/GeneratorManagerInterface.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\GeneratorManagers\\Interfaces;\n\nuse LaravelMigrationGenerator\\Definitions\\TableDefinition;\nuse LaravelMigrationGenerator\\Definitions\\ViewDefinition;\n\ninterface GeneratorManagerInterface\n{\n    public static function driver(): string;\n\n    public function handle(string $basePath, array $tableNames = [], array $viewNames = []);\n\n    public function addTableDefinition(TableDefinition $definition);\n\n    public function addViewDefinition(ViewDefinition $definition);\n\n    public function getTableDefinitions(): array;\n\n    public function getViewDefinitions(): array;\n}\n"
  },
  {
    "path": "src/GeneratorManagers/MySQLGeneratorManager.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\GeneratorManagers;\n\nuse Illuminate\\Support\\Facades\\DB;\nuse Illuminate\\Support\\Str;\nuse LaravelMigrationGenerator\\Definitions\\TableDefinition;\nuse LaravelMigrationGenerator\\GeneratorManagers\\Interfaces\\GeneratorManagerInterface;\nuse LaravelMigrationGenerator\\Generators\\MySQL\\TableGenerator;\nuse LaravelMigrationGenerator\\Generators\\MySQL\\ViewGenerator;\n\nclass MySQLGeneratorManager extends BaseGeneratorManager implements GeneratorManagerInterface\n{\n    public static function driver(): string\n    {\n        return 'mysql';\n    }\n\n    public function init()\n    {\n        $tables = DB::select('SHOW FULL TABLES');\n\n        foreach ($tables as $rowNumber => $table) {\n            $tableData = (array) $table;\n            $table = $tableData[array_key_first($tableData)];\n            $tableType = $tableData['Table_type'];\n            if ($tableType === 'BASE TABLE') {\n                $this->addTableDefinition(TableGenerator::init($table)->definition());\n            } elseif ($tableType === 'VIEW') {\n                $this->addViewDefinition(ViewGenerator::init($table)->definition());\n            }\n        }\n    }\n\n    public function addTableDefinition(TableDefinition $tableDefinition): BaseGeneratorManager\n    {\n        $prefix = config('database.connections.'.DB::getDefaultConnection().'.prefix', '');\n        if (! empty($prefix) && Str::startsWith($tableDefinition->getTableName(), $prefix)) {\n            $tableDefinition->setTableName(Str::replaceFirst($prefix, '', $tableDefinition->getTableName()));\n        }\n\n        return parent::addTableDefinition($tableDefinition);\n    }\n}\n"
  },
  {
    "path": "src/Generators/BaseTableGenerator.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Generators;\n\nuse LaravelMigrationGenerator\\Definitions\\TableDefinition;\nuse LaravelMigrationGenerator\\Generators\\Concerns\\CleansUpColumnIndices;\nuse LaravelMigrationGenerator\\Generators\\Concerns\\CleansUpForeignKeyIndices;\nuse LaravelMigrationGenerator\\Generators\\Concerns\\CleansUpMorphColumns;\nuse LaravelMigrationGenerator\\Generators\\Concerns\\CleansUpTimestampsColumn;\nuse LaravelMigrationGenerator\\Generators\\Interfaces\\TableGeneratorInterface;\n\nabstract class BaseTableGenerator implements TableGeneratorInterface\n{\n    use CleansUpColumnIndices;\n    use CleansUpForeignKeyIndices;\n    use CleansUpMorphColumns;\n    use CleansUpTimestampsColumn;\n\n    protected array $rows = [];\n\n    protected TableDefinition $definition;\n\n    public function __construct(string $tableName, array $rows = [])\n    {\n        $this->definition = new TableDefinition([\n            'driver' => static::driver(),\n            'tableName' => $tableName,\n        ]);\n        $this->rows = $rows;\n    }\n\n    public function definition(): TableDefinition\n    {\n        return $this->definition;\n    }\n\n    abstract public function resolveStructure();\n\n    abstract public function parse();\n\n    public static function init(string $tableName, array $rows = [])\n    {\n        $instance = (new static($tableName, $rows));\n\n        if ($instance->shouldResolveStructure()) {\n            $instance->resolveStructure();\n        }\n\n        $instance->parse();\n        $instance->cleanUp();\n\n        return $instance;\n    }\n\n    public function shouldResolveStructure(): bool\n    {\n        return count($this->rows) === 0;\n    }\n\n    public function cleanUp(): void\n    {\n        $this->cleanUpForeignKeyIndices();\n\n        $this->cleanUpMorphColumns();\n\n        if (! config('laravel-migration-generator.definitions.use_defined_datatype_on_timestamp')) {\n            $this->cleanUpTimestampsColumn();\n        }\n\n        $this->cleanUpColumnsWithIndices();\n    }\n}\n"
  },
  {
    "path": "src/Generators/BaseViewGenerator.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Generators;\n\nuse LaravelMigrationGenerator\\Definitions\\ViewDefinition;\nuse LaravelMigrationGenerator\\Generators\\Interfaces\\ViewGeneratorInterface;\n\nabstract class BaseViewGenerator implements ViewGeneratorInterface\n{\n    protected ViewDefinition $definition;\n\n    public function __construct(string $viewName, ?string $schema = null)\n    {\n        $this->definition = new ViewDefinition([\n            'driver' => static::driver(),\n            'viewName' => $viewName,\n            'schema' => $schema,\n        ]);\n    }\n\n    public function definition(): ViewDefinition\n    {\n        return $this->definition;\n    }\n\n    public static function init(string $viewName, ?string $schema = null)\n    {\n        $obj = new static($viewName, $schema);\n        if ($schema === null) {\n            $obj->resolveSchema();\n        }\n        $obj->parse();\n\n        return $obj;\n    }\n}\n"
  },
  {
    "path": "src/Generators/Concerns/CleansUpColumnIndices.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Generators\\Concerns;\n\nuse LaravelMigrationGenerator\\Generators\\BaseTableGenerator;\n\n/**\n * Trait CleansUpColumnIndices\n *\n * @mixin BaseTableGenerator\n */\ntrait CleansUpColumnIndices\n{\n    protected function cleanUpColumnsWithIndices(): void\n    {\n        foreach ($this->definition()->getIndexDefinitions() as &$index) {\n            /** @var \\LaravelMigrationGenerator\\Definitions\\IndexDefinition $index */\n            if (! $index->isWritable()) {\n                continue;\n            }\n            $columns = $index->getIndexColumns();\n\n            foreach ($columns as $indexColumn) {\n                foreach ($this->definition()->getColumnDefinitions() as $column) {\n                    if ($column->getColumnName() === $indexColumn) {\n                        $indexType = $index->getIndexType();\n                        $isMultiColumnIndex = $index->isMultiColumnIndex();\n\n                        if ($indexType === 'primary' && ! $isMultiColumnIndex) {\n                            $column->setPrimary(true)->addIndexDefinition($index);\n                            $index->markAsWritable(false);\n                        } elseif ($indexType === 'index' && ! $isMultiColumnIndex) {\n                            $isForeignKeyIndex = false;\n                            foreach ($this->definition()->getIndexDefinitions() as $innerIndex) {\n                                $innerIndexColumns = $innerIndex->getIndexColumns();\n                                if ($innerIndex->getIndexType() === 'foreign' && ! $innerIndex->isMultiColumnIndex() && ! empty($innerIndexColumns) && $innerIndexColumns[0] == $column->getColumnName()) {\n                                    $isForeignKeyIndex = true;\n\n                                    break;\n                                }\n                            }\n                            if ($isForeignKeyIndex === false) {\n                                $column->setIndex(true)->addIndexDefinition($index);\n                            }\n                            $index->markAsWritable(false);\n                        } elseif ($indexType === 'unique' && ! $isMultiColumnIndex) {\n                            $column->setUnique(true)->addIndexDefinition($index);\n                            $index->markAsWritable(false);\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Generators/Concerns/CleansUpForeignKeyIndices.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Generators\\Concerns;\n\nuse LaravelMigrationGenerator\\Generators\\BaseTableGenerator;\n\n/**\n * Trait CleansUpForeignKeyIndices\n *\n * @mixin BaseTableGenerator\n */\ntrait CleansUpForeignKeyIndices\n{\n    protected function cleanUpForeignKeyIndices(): void\n    {\n        $indexDefinitions = $this->definition()->getIndexDefinitions();\n        foreach ($indexDefinitions as $index) {\n            /** @var \\LaravelMigrationGenerator\\Definitions\\IndexDefinition $index */\n            if ($index->getIndexType() === 'index') {\n                //look for corresponding foreign key for this index\n                $columns = $index->getIndexColumns();\n                $indexName = $index->getIndexName();\n\n                foreach ($indexDefinitions as $innerIndex) {\n                    /** @var \\LaravelMigrationGenerator\\Definitions\\IndexDefinition $innerIndex */\n                    if ($innerIndex->getIndexName() !== $indexName) {\n                        if ($innerIndex->getIndexType() === 'foreign') {\n                            $cols = $innerIndex->getIndexColumns();\n                            if (count(array_intersect($columns, $cols)) === count($columns)) {\n                                //has same columns\n                                $index->markAsWritable(false);\n\n                                break;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Generators/Concerns/CleansUpMorphColumns.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Generators\\Concerns;\n\nuse Illuminate\\Support\\Str;\nuse LaravelMigrationGenerator\\Definitions\\ColumnDefinition;\nuse LaravelMigrationGenerator\\Generators\\BaseTableGenerator;\n\n/**\n * Trait CleansUpMorphColumns\n *\n * @mixin BaseTableGenerator\n */\ntrait CleansUpMorphColumns\n{\n    protected function cleanUpMorphColumns(): void\n    {\n        $morphColumns = [];\n\n        foreach ($this->definition()->getColumnDefinitions() as &$column) {\n            if (Str::endsWith($columnName = $column->getColumnName(), ['_id', '_type'])) {\n                $pieces = explode('_', $columnName);\n                $type = array_pop($pieces); //pop off id or type\n                $morphColumn = implode('_', $pieces);\n                $morphColumns[$morphColumn][$type] = $column;\n            }\n        }\n\n        foreach ($morphColumns as $columnName => $fields) {\n            if (count($fields) === 2) {\n                /** @var ColumnDefinition $idField */\n                $idField = $fields['id'];\n                /** @var ColumnDefinition $typeField */\n                $typeField = $fields['type'];\n\n                if (! ($idField->isUUID() || Str::contains($idField->getMethodName(), 'integer'))) {\n                    //should only be a uuid field or integer\n                    continue;\n                }\n                if ($typeField->getMethodName() != 'string') {\n                    //should only be a string field\n                    continue;\n                }\n\n                if ($idField->isUUID()) {\n                    //UUID morph\n                    $idField\n                        ->setMethodName('uuidMorphs')\n                        ->setMethodParameters([])\n                        ->setColumnName($columnName);\n                } else {\n                    //regular morph\n                    $idField\n                        ->setMethodName('morphs')\n                        ->setColumnName($columnName);\n                }\n                $typeField->markAsWritable(false);\n\n                foreach ($this->definition->getIndexDefinitions() as $index) {\n                    $columns = $index->getIndexColumns();\n                    $morphColumns = [$columnName.'_id', $columnName.'_type'];\n\n                    if (count($columns) == count($morphColumns) && array_diff($columns, $morphColumns) === array_diff($morphColumns, $columns)) {\n                        $index->markAsWritable(false);\n\n                        break;\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Generators/Concerns/CleansUpTimestampsColumn.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Generators\\Concerns;\n\nuse LaravelMigrationGenerator\\Generators\\BaseTableGenerator;\n\n/**\n * Trait CleansUpTimestampsColumn\n *\n * @mixin BaseTableGenerator\n */\ntrait CleansUpTimestampsColumn\n{\n    protected function cleanUpTimestampsColumn(): void\n    {\n        $timestampColumns = [];\n        foreach ($this->definition()->getColumnDefinitions() as &$column) {\n            $columnName = $column->getColumnName();\n            if ($columnName === 'created_at') {\n                $timestampColumns['created_at'] = $column;\n            } elseif ($columnName === 'updated_at') {\n                $timestampColumns['updated_at'] = $column;\n            }\n            if (count($timestampColumns) === 2) {\n                foreach ($timestampColumns as $timestampColumn) {\n                    if ($timestampColumn->useCurrent() || $timestampColumn->useCurrentOnUpdate()) {\n                        //don't convert to a `timestamps()` method if useCurrent is used\n\n                        return;\n                    }\n                }\n                $timestampColumns['created_at']\n                    ->setColumnName(null)\n                    ->setMethodName('timestamps')\n                    ->setNullable(false);\n                $timestampColumns['updated_at']->markAsWritable(false);\n\n                break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Generators/Concerns/WritesToFile.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Generators\\Concerns;\n\ntrait WritesToFile\n{\n    public function write(string $basePath, $index = 0, string $tabCharacter = '    '): void\n    {\n        if (method_exists($this, 'isWritable') && ! $this->isWritable()) {\n            return;\n        }\n\n        $stub = $this->generateStub($tabCharacter);\n\n        $fileName = $this->getStubFileName($index);\n        file_put_contents($basePath.'/'.$fileName, $stub);\n    }\n}\n"
  },
  {
    "path": "src/Generators/Concerns/WritesViewsToFile.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Generators\\Concerns;\n\nuse Illuminate\\Support\\Str;\nuse LaravelMigrationGenerator\\Helpers\\ConfigResolver;\n\ntrait WritesViewsToFile\n{\n    use WritesToFile;\n\n    public function stubNameVariables()\n    {\n        return [\n            'ViewName:Studly' => Str::studly($this->viewName),\n            'ViewName:Lowercase' => strtolower($this->viewName),\n            'ViewName' => $this->viewName,\n            'Timestamp' => app('laravel-migration-generator:time')->format('Y_m_d_His'),\n        ];\n    }\n\n    protected function getStubFileName()\n    {\n        $driver = static::driver();\n\n        $baseStubFileName = ConfigResolver::viewNamingScheme($driver);\n        foreach ($this->stubNameVariables() as $variable => $replacement) {\n            if (preg_match(\"/\\[\".$variable.\"\\]/i\", $baseStubFileName) === 1) {\n                $baseStubFileName = preg_replace(\"/\\[\".$variable.\"\\]/i\", $replacement, $baseStubFileName);\n            }\n        }\n\n        return $baseStubFileName;\n    }\n\n    protected function getStubPath()\n    {\n        $driver = static::driver();\n\n        if (file_exists($overridden = resource_path('stubs/vendor/laravel-migration-generator/'.$driver.'-view.stub'))) {\n            return $overridden;\n        }\n\n        if (file_exists($overridden = resource_path('stubs/vendor/laravel-migration-generator/view.stub'))) {\n            return $overridden;\n        }\n\n        return __DIR__.'/../../../stubs/view.stub';\n    }\n\n    protected function generateStub($tabCharacter = '    ')\n    {\n        $tab = str_repeat($tabCharacter, 3);\n\n        $schema = $this->getSchema();\n        $stub = file_get_contents($this->getStubPath());\n        $stub = str_replace('[ViewName:Studly]', Str::studly($this->viewName), $stub);\n        $stub = str_replace('[ViewName]', $this->viewName, $stub);\n        $stub = str_replace('[Schema]', $tab.$schema, $stub);\n\n        return $stub;\n    }\n}\n"
  },
  {
    "path": "src/Generators/Interfaces/TableGeneratorInterface.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Generators\\Interfaces;\n\nuse LaravelMigrationGenerator\\Definitions\\TableDefinition;\n\ninterface TableGeneratorInterface\n{\n    public static function driver(): string;\n\n    public function shouldResolveStructure(): bool;\n\n    public function resolveStructure();\n\n    public function parse();\n\n    public function cleanUp();\n\n    public function definition(): TableDefinition;\n}\n"
  },
  {
    "path": "src/Generators/Interfaces/ViewGeneratorInterface.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Generators\\Interfaces;\n\ninterface ViewGeneratorInterface\n{\n    public static function driver(): string;\n\n    public function parse();\n\n    public function resolveSchema();\n}\n"
  },
  {
    "path": "src/Generators/MySQL/TableGenerator.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Generators\\MySQL;\n\nuse Illuminate\\Support\\Facades\\DB;\nuse Illuminate\\Support\\Str;\nuse LaravelMigrationGenerator\\Generators\\BaseTableGenerator;\nuse LaravelMigrationGenerator\\Tokenizers\\MySQL\\ColumnTokenizer;\nuse LaravelMigrationGenerator\\Tokenizers\\MySQL\\IndexTokenizer;\n\n/**\n * Class TableGenerator\n */\nclass TableGenerator extends BaseTableGenerator\n{\n    public static function driver(): string\n    {\n        return 'mysql';\n    }\n\n    public function resolveStructure()\n    {\n        $structure = DB::select('SHOW CREATE TABLE `'.$this->definition()->getTableName().'`');\n        $structure = $structure[0];\n        $structure = (array) $structure;\n        if (isset($structure['Create Table'])) {\n            $lines = explode(\"\\n\", $structure['Create Table']);\n\n            array_shift($lines); //get rid of first line\n            array_pop($lines); //get rid of last line\n\n            $lines = array_map(fn ($item) => trim($item), $lines);\n            $this->rows = $lines;\n        } else {\n            $this->rows = [];\n        }\n    }\n\n    protected function isColumnLine($line)\n    {\n        return ! Str::startsWith($line, ['KEY', 'PRIMARY', 'UNIQUE', 'FULLTEXT', 'CONSTRAINT']);\n    }\n\n    public function parse()\n    {\n        foreach ($this->rows as $line) {\n            if ($this->isColumnLine($line)) {\n                $tokenizer = ColumnTokenizer::parse($line);\n                $this->definition()->addColumnDefinition($tokenizer->definition());\n            } else {\n                $tokenizer = IndexTokenizer::parse($line);\n                $this->definition()->addIndexDefinition($tokenizer->definition());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Generators/MySQL/ViewGenerator.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Generators\\MySQL;\n\nuse Illuminate\\Support\\Facades\\DB;\nuse LaravelMigrationGenerator\\Generators\\BaseViewGenerator;\nuse LaravelMigrationGenerator\\Generators\\Interfaces\\ViewGeneratorInterface;\n\nclass ViewGenerator extends BaseViewGenerator implements ViewGeneratorInterface\n{\n    public static function driver(): string\n    {\n        return 'mysql';\n    }\n\n    public function resolveSchema()\n    {\n        $structure = DB::select('SHOW CREATE VIEW `'.$this->definition()->getViewName().'`');\n        $structure = $structure[0];\n        $structure = (array) $structure;\n        if (isset($structure['Create View'])) {\n            $this->definition()->setSchema($structure['Create View']);\n        }\n    }\n\n    public function parse()\n    {\n        $schema = $this->definition()->getSchema();\n        if (preg_match('/CREATE(.*?)VIEW/', $schema, $matches)) {\n            $schema = str_replace($matches[1], ' ', $schema);\n        }\n\n        if (preg_match_all('/isnull\\((.+?)\\)/', $schema, $matches)) {\n            foreach ($matches[0] as $key => $match) {\n                $schema = str_replace($match, $matches[1][$key].' IS NULL', $schema);\n            }\n        }\n        if (preg_match('/collate utf8mb4_unicode_ci/', $schema)) {\n            $schema = str_replace('collate utf8mb4_unicode_ci', '', $schema);\n        }\n        $this->definition()->setSchema($schema);\n    }\n}\n"
  },
  {
    "path": "src/Helpers/ConfigResolver.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Helpers;\n\nclass ConfigResolver\n{\n    protected static function resolver(string $configKey, string $driver)\n    {\n        return ($override = config('laravel-migration-generator.'.$driver.'.'.$configKey)) !== null ?\n            $override : config('laravel-migration-generator.'.$configKey);\n    }\n\n    public static function tableNamingScheme(string $driver)\n    {\n        return static::resolver('table_naming_scheme', $driver);\n    }\n\n    public static function viewNamingScheme(string $driver)\n    {\n        return static::resolver('view_naming_scheme', $driver);\n    }\n\n    public static function path(string $driver)\n    {\n        return static::resolver('path', $driver);\n    }\n\n    public static function skippableTables(string $driver)\n    {\n        return array_map('trim', explode(',', static::resolver('skippable_tables', $driver)));\n    }\n\n    public static function skippableViews(string $driver)\n    {\n        return array_map('trim', explode(',', static::resolver('skippable_views', $driver)));\n    }\n}\n"
  },
  {
    "path": "src/Helpers/DependencyResolver.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Helpers;\n\nuse LaravelMigrationGenerator\\Definitions\\IndexDefinition;\nuse LaravelMigrationGenerator\\Definitions\\TableDefinition;\nuse MJS\\TopSort\\Implementations\\FixedArraySort;\n\nclass DependencyResolver\n{\n    /** @var array<TableDefinition> */\n    protected array $tableDefinitions = [];\n\n    /** @var array<TableDefinition> */\n    protected array $sorted = [];\n\n    public function __construct(array $tableDefinitions)\n    {\n        $this->tableDefinitions = $tableDefinitions;\n\n        $this->build();\n    }\n\n    protected function build()\n    {\n        /** @var TableDefinition[] $keyedDefinitions */\n        $keyedDefinitions = collect($this->tableDefinitions)\n            ->keyBy(function (TableDefinition $tableDefinition) {\n                return $tableDefinition->getTableName();\n            });\n        $dependencies = [];\n        foreach ($this->tableDefinitions as $tableDefinition) {\n            $dependencies[$tableDefinition->getTableName()] = [];\n        }\n        foreach ($this->tableDefinitions as $tableDefinition) {\n            foreach ($tableDefinition->getForeignKeyDefinitions() as $indexDefinition) {\n                $referencedTable = $indexDefinition->getForeignReferencedTable();\n                if (empty($referencedTable)) {\n                    continue;\n                }\n                if ($referencedTable === $tableDefinition->getTableName()) {\n                    continue;\n                }\n                if (! in_array($referencedTable, $dependencies[$tableDefinition->getTableName()])) {\n                    $dependencies[$tableDefinition->getTableName()][] = $referencedTable;\n                }\n            }\n        }\n\n        $sorter = new FixedArraySort();\n        $circulars = [];\n        $sorter->setCircularInterceptor(function ($nodes) use (&$circulars) {\n            $circulars[] = [$nodes[count($nodes) - 2], $nodes[count($nodes) - 1]];\n        });\n        foreach ($dependencies as $table => $dependencyArray) {\n            $sorter->add($table, $dependencyArray);\n        }\n        $sorted = $sorter->sort();\n        $definitions = collect($sorted)->map(function ($item) use ($keyedDefinitions) {\n            return $keyedDefinitions[$item];\n        })->toArray();\n\n        foreach ($circulars as $groups) {\n            [$start, $end] = $groups;\n            $startDefinition = $keyedDefinitions[$start];\n            $indicesForStart = collect($startDefinition->getForeignKeyDefinitions())\n                ->filter(function (IndexDefinition $index) use ($end) {\n                    return $index->getForeignReferencedTable() == $end;\n                });\n            foreach ($indicesForStart as $index) {\n                $startDefinition->removeIndexDefinition($index);\n            }\n            if (! in_array($start, $sorted)) {\n                $definitions[] = $startDefinition;\n            }\n\n            $endDefinition = $keyedDefinitions[$end];\n\n            $indicesForEnd = collect($endDefinition->getForeignKeyDefinitions())\n                ->filter(function (IndexDefinition $index) use ($start) {\n                    return $index->getForeignReferencedTable() == $start;\n                });\n            foreach ($indicesForEnd as $index) {\n                $endDefinition->removeIndexDefinition($index);\n            }\n            if (! in_array($end, $sorted)) {\n                $definitions[] = $endDefinition;\n            }\n\n            $definitions[] = new TableDefinition([\n                'tableName' => $startDefinition->getTableName(),\n                'driver' => $startDefinition->getDriver(),\n                'columnDefinitions' => [],\n                'indexDefinitions' => $indicesForStart->toArray(),\n            ]);\n\n            $definitions[] = new TableDefinition([\n                'tableName' => $endDefinition->getTableName(),\n                'driver' => $endDefinition->getDriver(),\n                'columnDefinitions' => [],\n                'indexDefinitions' => $indicesForEnd->toArray(),\n            ]);\n        }\n        $this->sorted = $definitions;\n    }\n\n    /**\n     * @return TableDefinition[]\n     */\n    public function getDependencyOrder(): array\n    {\n        return $this->sorted;\n    }\n}\n"
  },
  {
    "path": "src/Helpers/Formatter.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Helpers;\n\nclass Formatter\n{\n    private array $lines = [];\n\n    private string $tabCharacter;\n\n    public function __construct(string $tabCharacter = '    ')\n    {\n        $this->tabCharacter = $tabCharacter;\n    }\n\n    public function line(string $data, $indentTimes = 0)\n    {\n        $this->lines[] = str_repeat($this->tabCharacter, $indentTimes).$data;\n\n        return function ($data) use ($indentTimes) {\n            return $this->line($data, $indentTimes + 1);\n        };\n    }\n\n    public function render($extraIndent = 0)\n    {\n        $lines = $this->lines;\n        if ($extraIndent > 0) {\n            $lines = collect($lines)->map(function ($item, $index) use ($extraIndent) {\n                if ($index === 0) {\n                    return $item;\n                }\n\n                return str_repeat($this->tabCharacter, $extraIndent).$item;\n            })->toArray();\n        }\n\n        return implode(\"\\n\", $lines);\n    }\n\n    public function replaceOnLine($toReplace, $body)\n    {\n        if (preg_match('/^(\\s+)?'.preg_quote($toReplace).'/m', $body, $matches) !== false) {\n            $gap = $matches[1] ?? '';\n            $numSpaces = strlen($this->tabCharacter);\n            if ($numSpaces === 0) {\n                $startingTabIndent = 0;\n            } else {\n                $startingTabIndent = (int) (strlen($gap) / $numSpaces);\n            }\n\n            return preg_replace('/'.preg_quote($toReplace).'/', $this->render($startingTabIndent), $body);\n        }\n\n        return $body;\n    }\n\n    public static function replace($tabCharacter, $toReplace, $replacement, $body)\n    {\n        $formatter = new static($tabCharacter);\n        foreach (explode(\"\\n\", $replacement) as $line) {\n            $formatter->line($line);\n        }\n\n        return $formatter->replaceOnLine($toReplace, $body);\n    }\n}\n"
  },
  {
    "path": "src/Helpers/ValueToString.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Helpers;\n\nuse Illuminate\\Support\\Str;\n\nclass ValueToString\n{\n    public static function castFloat($value)\n    {\n        return 'float$:'.$value;\n    }\n\n    public static function castBinary($value)\n    {\n        return 'binary$:'.$value;\n    }\n\n    public static function isCastedValue($value)\n    {\n        return Str::startsWith($value, ['float$:', 'binary$:']);\n    }\n\n    public static function parseCastedValue($value)\n    {\n        if (Str::startsWith($value, 'float$:')) {\n            return str_replace('float$:', '', $value);\n        }\n        if (Str::startsWith($value, 'binary$:')) {\n            return 'b\\''.str_replace('binary$:', '', $value).'\\'';\n        }\n\n        return $value;\n    }\n\n    /**\n     * Escape a string value for safe inclusion in generated PHP code.\n     * Prevents PHP injection through crafted index/column names.\n     *\n     * @param  string  $value  The value to escape\n     * @param  bool  $singleQuote  Whether the string will be wrapped in single quotes (true) or double quotes (false)\n     */\n    public static function escape(string $value, bool $singleQuote = true): string\n    {\n        // Escape backslashes first\n        $escaped = str_replace('\\\\', '\\\\\\\\', $value);\n\n        // Then escape the quote character being used as delimiter\n        if ($singleQuote) {\n            return str_replace('\\'', '\\\\\\'', $escaped);\n        } else {\n            return str_replace('\"', '\\\\\"', $escaped);\n        }\n    }\n\n    public static function make($value, $singleOutArray = false, $singleQuote = true)\n    {\n        $quote = $singleQuote ? '\\'' : '\"';\n        if ($value === null) {\n            return 'null';\n        } elseif (is_array($value)) {\n            if ($singleOutArray && count($value) === 1) {\n                return $quote.static::escape($value[0], $singleQuote).$quote;\n            }\n\n            return '['.collect($value)->map(fn ($item) => $quote.static::escape($item, $singleQuote).$quote)->implode(', ').']';\n        } elseif (is_int($value) || is_float($value)) {\n            return $value;\n        }\n\n        if (static::isCastedValue($value)) {\n            return static::parseCastedValue($value);\n        }\n\n        if (Str::startsWith($value, $quote) && Str::endsWith($value, $quote)) {\n            return $value;\n        }\n\n        return $quote.static::escape($value, $singleQuote).$quote;\n    }\n}\n"
  },
  {
    "path": "src/Helpers/WritableTrait.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Helpers;\n\ntrait WritableTrait\n{\n    public bool $writable = true;\n\n    public function markAsWritable(bool $writable = true)\n    {\n        $this->writable = $writable;\n\n        return $this;\n    }\n\n    public function isWritable()\n    {\n        return $this->writable;\n    }\n}\n"
  },
  {
    "path": "src/LaravelMigrationGeneratorProvider.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator;\n\nuse Illuminate\\Database\\Events\\MigrationsEnded;\nuse Illuminate\\Support\\Facades\\Artisan;\nuse Illuminate\\Support\\Facades\\Event;\nuse Illuminate\\Support\\ServiceProvider;\nuse LaravelMigrationGenerator\\Commands\\GenerateMigrationsCommand;\n\nclass LaravelMigrationGeneratorProvider extends ServiceProvider\n{\n    public function boot()\n    {\n        $this->mergeConfigFrom(\n            __DIR__.'/../config/laravel-migration-generator.php',\n            'laravel-migration-generator'\n        );\n\n        $this->publishes([\n            __DIR__.'/../stubs' => resource_path('stubs/vendor/laravel-migration-generator'),\n            __DIR__.'/../config/laravel-migration-generator.php' => config_path('laravel-migration-generator.php'),\n        ]);\n\n        if ($this->app->runningInConsole()) {\n            $this->app->instance('laravel-migration-generator:time', now());\n            $this->commands([\n                GenerateMigrationsCommand::class,\n            ]);\n        }\n        if (config('laravel-migration-generator.run_after_migrations') && config('app.env') === 'local') {\n            Event::listen(MigrationsEnded::class, function () {\n                Artisan::call('generate:migrations');\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "src/Tokenizers/BaseColumnTokenizer.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Tokenizers;\n\nuse LaravelMigrationGenerator\\Definitions\\ColumnDefinition;\nuse LaravelMigrationGenerator\\Tokenizers\\Interfaces\\ColumnTokenizerInterface;\n\nabstract class BaseColumnTokenizer extends BaseTokenizer implements ColumnTokenizerInterface\n{\n    protected ColumnDefinition $definition;\n\n    public function __construct(string $value)\n    {\n        $this->definition = new ColumnDefinition();\n        parent::__construct($value);\n    }\n\n    public function definition(): ColumnDefinition\n    {\n        return $this->definition;\n    }\n}\n"
  },
  {
    "path": "src/Tokenizers/BaseIndexTokenizer.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Tokenizers;\n\nuse LaravelMigrationGenerator\\Definitions\\IndexDefinition;\nuse LaravelMigrationGenerator\\Tokenizers\\Interfaces\\IndexTokenizerInterface;\n\nabstract class BaseIndexTokenizer extends BaseTokenizer implements IndexTokenizerInterface\n{\n    protected IndexDefinition $definition;\n\n    public function __construct(string $value)\n    {\n        $this->definition = new IndexDefinition();\n        parent::__construct($value);\n    }\n\n    public function definition(): IndexDefinition\n    {\n        return $this->definition;\n    }\n}\n"
  },
  {
    "path": "src/Tokenizers/BaseTokenizer.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Tokenizers;\n\nabstract class BaseTokenizer\n{\n    protected $tokens = [];\n\n    private string $value;\n\n    private const SPACE_REPLACER = '&!@';\n\n    private const SINGLE_QUOTE_REPLACER = '!*@';\n\n    private const EMPTY_STRING_REPLACER = '$$EMPTY_STRING';\n\n    public function __construct(string $value)\n    {\n        $this->value = $value;\n        $prune = false;\n        $pruneSingleQuotes = false;\n\n        if (preg_match(\"/(DEFAULT|COMMENT) ''/\", $value, $matches)) {\n            $value = str_replace($matches[1].' \\'\\'', $matches[1].' '.self::EMPTY_STRING_REPLACER, $value);\n        }\n\n        //first get rid of any single quoted stuff with '' around it\n        if (preg_match_all('/\\'\\'(.+?)\\'\\'/', $value, $matches)) {\n            foreach ($matches[0] as $key => $singleQuoted) {\n                $toReplace = $singleQuoted;\n                $value = str_replace($toReplace, self::SINGLE_QUOTE_REPLACER.$matches[1][$key].self::SINGLE_QUOTE_REPLACER, $value);\n                $pruneSingleQuotes = true;\n            }\n        }\n\n        if (preg_match_all(\"/'(.*?)'/\", $value, $matches)) {\n            foreach ($matches[0] as $quoteWithSpace) {\n                //we've got an enum or set that has spaces in the text\n                //so we'll convert to a different character so it doesn't get pruned\n                $toReplace = $quoteWithSpace;\n                $value = str_replace($toReplace, str_replace(' ', self::SPACE_REPLACER, $toReplace), $value);\n                $prune = true;\n            }\n        }\n        $value = str_replace(self::EMPTY_STRING_REPLACER, '\\'\\'', $value);\n        $this->tokens = array_map(function ($item) {\n            return trim($item, ', ');\n        }, str_getcsv($value, ' ', \"'\"));\n\n        if ($prune) {\n            $this->tokens = array_map(function ($item) {\n                return str_replace(self::SPACE_REPLACER, ' ', $item);\n            }, $this->tokens);\n        }\n        if ($pruneSingleQuotes) {\n            $this->tokens = array_map(function ($item) {\n                return str_replace(self::SINGLE_QUOTE_REPLACER, '\\'', $item);\n            }, $this->tokens);\n        }\n    }\n\n    public static function make(string $line)\n    {\n        return new static($line);\n    }\n\n    /**\n     * @return static\n     */\n    public static function parse(string $line)\n    {\n        return (new static($line))->tokenize();\n    }\n\n    protected function parseColumn($value)\n    {\n        return trim($value, '` ');\n    }\n\n    protected function columnsToArray($string)\n    {\n        $string = trim($string, '()');\n\n        return array_map(fn ($item) => $this->parseColumn($item), explode(',', $string));\n    }\n\n    protected function consume()\n    {\n        return array_shift($this->tokens);\n    }\n\n    protected function putBack($value)\n    {\n        array_unshift($this->tokens, $value);\n    }\n}\n"
  },
  {
    "path": "src/Tokenizers/Interfaces/ColumnTokenizerInterface.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Tokenizers\\Interfaces;\n\nuse LaravelMigrationGenerator\\Definitions\\ColumnDefinition;\n\ninterface ColumnTokenizerInterface\n{\n    public function tokenize(): self;\n\n    public function definition(): ColumnDefinition;\n}\n"
  },
  {
    "path": "src/Tokenizers/Interfaces/IndexTokenizerInterface.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Tokenizers\\Interfaces;\n\nuse LaravelMigrationGenerator\\Definitions\\IndexDefinition;\n\ninterface IndexTokenizerInterface\n{\n    public function tokenize(): self;\n\n    public function definition(): IndexDefinition;\n}\n"
  },
  {
    "path": "src/Tokenizers/MySQL/ColumnTokenizer.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Tokenizers\\MySQL;\n\nuse Illuminate\\Database\\Schema\\Builder;\nuse Illuminate\\Support\\Str;\nuse LaravelMigrationGenerator\\Helpers\\ValueToString;\nuse LaravelMigrationGenerator\\Tokenizers\\BaseColumnTokenizer;\n\nclass ColumnTokenizer extends BaseColumnTokenizer\n{\n    protected $columnDataType;\n\n    /**\n     * MySQL provides a ZEROFILL property for ints which is not an ANSI compliant modifier\n     *\n     * @var bool\n     */\n    protected $zeroFill = false;\n\n    public function tokenize(): self\n    {\n        $this->consumeColumnName();\n        $this->consumeColumnType();\n        if ($this->isNumberType()) {\n            $this->consumeUnsigned();\n            $this->consumeZeroFill();\n        }\n        if ($this->isTextType()) {\n            //possibly has a character set\n            $this->consumeCharacterSet();\n\n            //has collation data most likely\n            $this->consumeCollation();\n        }\n\n        $this->consumeNullable();\n\n        $this->consumeDefaultValue();\n        if ($this->isNumberType()) {\n            $this->consumeAutoIncrement();\n            $this->consumeKeyConstraints();\n        }\n\n        $this->consumeGenerated();\n\n        if ($this->columnDataType == 'timestamp' || $this->columnDataType == 'datetime') {\n            $this->consumeTimestamp();\n        }\n\n        $this->consumeComment();\n\n        return $this;\n    }\n\n    //region Consumers\n\n    protected function consumeColumnName()\n    {\n        $this->definition->setColumnName($this->parseColumn($this->consume()));\n    }\n\n    protected function consumeZeroFill()\n    {\n        $nextPiece = $this->consume();\n\n        if (strtoupper($nextPiece) === 'ZEROFILL') {\n            $this->zeroFill = true;\n        } else {\n            $this->putBack($nextPiece);\n        }\n    }\n\n    protected function consumeColumnType()\n    {\n        $originalColumnType = $columnType = $this->consume();\n        $hasConstraints = Str::contains($columnType, '(');\n\n        if ($hasConstraints) {\n            $columnType = explode('(', $columnType)[0];\n        }\n\n        $this->columnDataType = strtolower($columnType);\n\n        $this->resolveColumnMethod();\n        if ($hasConstraints) {\n            preg_match(\"/\\((.+?)\\)/\", $originalColumnType, $constraintMatches);\n            $matches = explode(',', $constraintMatches[1]);\n            $this->resolveColumnConstraints($matches);\n        }\n    }\n\n    private function consumeAutoIncrement()\n    {\n        $piece = $this->consume();\n        if (strtoupper($piece) === 'AUTO_INCREMENT') {\n            $this->definition->setAutoIncrementing(true);\n        } else {\n            $this->putBack($piece);\n        }\n    }\n\n    protected function consumeNullable()\n    {\n        $piece = $this->consume();\n        if (strtoupper($piece) === 'NOT') {\n            $this->consume(); //next is NULL\n            $this->definition->setNullable(false);\n        } elseif (strtoupper($piece) === 'NULL') {\n            $this->definition->setNullable(true);\n        } else {\n            //something else\n            $this->putBack($piece);\n        }\n\n        if (Str::contains($this->columnDataType, 'text')) {\n            //text column types are explicitly nullable unless set to NOT NULL\n            if ($this->definition->isNullable() === null) {\n                $this->definition->setNullable(true);\n            }\n        }\n    }\n\n    protected function consumeDefaultValue()\n    {\n        $piece = $this->consume();\n        if (strtoupper($piece) === 'DEFAULT') {\n            $this->definition->setDefaultValue($this->consume());\n\n            if (strtoupper($this->definition->getDefaultValue()) === 'NULL') {\n                $this->definition\n                    ->setNullable(true)\n                    ->setDefaultValue(null);\n            } elseif (strtoupper($this->definition->getDefaultValue()) === 'CURRENT_TIMESTAMP') {\n                $this->definition\n                    ->setDefaultValue(null)\n                    ->setUseCurrent(true);\n            } elseif (preg_match(\"/b'([01]+)'/i\", $this->definition->getDefaultValue(), $matches)) {\n                // Binary digit, so let's convert to PHP's version\n                $this->definition->setDefaultValue(ValueToString::castBinary($matches[1]));\n            }\n            if ($this->definition->getDefaultValue() !== null) {\n                if ($this->isNumberType()) {\n                    if (Str::contains(strtoupper($this->columnDataType), 'INT')) {\n                        $this->definition->setDefaultValue((int) $this->definition->getDefaultValue());\n                    } else {\n                        //floats get converted to strings improperly, gotta do a string cast\n                        $this->definition->setDefaultValue(ValueToString::castFloat($this->definition->getDefaultValue()));\n                    }\n                } else {\n                    if (! $this->isBinaryType()) {\n                        $this->definition->setDefaultValue((string) $this->definition->getDefaultValue());\n                    }\n                }\n            }\n        } else {\n            $this->putBack($piece);\n        }\n    }\n\n    protected function consumeComment()\n    {\n        $piece = $this->consume();\n        if (strtoupper($piece) === 'COMMENT') {\n            // next piece is the comment content\n            $this->definition->setComment($this->consume());\n        } else {\n            $this->putBack($piece);\n        }\n    }\n\n    protected function consumeCharacterSet()\n    {\n        $piece = $this->consume();\n\n        if (strtoupper($piece) === 'CHARACTER') {\n            $this->consume(); // SET, throw it away\n\n            $this->definition->setCharacterSet($this->consume());\n        } else {\n            //something else\n            $this->putBack($piece);\n        }\n    }\n\n    protected function consumeCollation()\n    {\n        $piece = $this->consume();\n        if (strtoupper($piece) === 'COLLATE') {\n            //next piece is the collation type\n            $this->definition->setCollation($this->consume());\n        } else {\n            $this->putBack($piece);\n        }\n    }\n\n    private function consumeUnsigned()\n    {\n        $piece = $this->consume();\n        if (strtoupper($piece) === 'UNSIGNED') {\n            $this->definition->setUnsigned(true);\n        } else {\n            $this->putBack($piece);\n        }\n    }\n\n    private function consumeKeyConstraints()\n    {\n        $nextPiece = $this->consume();\n        if (strtoupper($nextPiece) === 'PRIMARY') {\n            $this->definition->setPrimary(true);\n\n            $next = $this->consume();\n            if (strtoupper($next) !== 'KEY') {\n                $this->putBack($next);\n            }\n        } elseif (strtoupper($nextPiece) === 'UNIQUE') {\n            $this->definition->setUnique(true);\n\n            $next = $this->consume();\n            if (strtoupper($next) !== 'KEY') {\n                $this->putBack($next);\n            }\n        } else {\n            $this->putBack($nextPiece);\n        }\n    }\n\n    private function consumeGenerated()\n    {\n        $canContinue = false;\n        $nextPiece = $this->consume();\n        if (strtoupper($nextPiece) === 'GENERATED') {\n            $piece = $this->consume();\n            if (strtoupper($piece) === 'ALWAYS') {\n                $this->consume(); // AS\n                $canContinue = true;\n            } else {\n                $this->putBack($piece);\n            }\n        } elseif (strtoupper($nextPiece) === 'AS') {\n            $canContinue = true;\n        }\n\n        if (! $canContinue) {\n            $this->putBack($nextPiece);\n\n            return;\n        }\n\n        $expressionPieces = [];\n        $parenthesisCounter = 0;\n        while ($pieceOfExpression = $this->consume()) {\n            $numOpeningParenthesis = substr_count($pieceOfExpression, '(');\n            $numClosingParenthesis = substr_count($pieceOfExpression, ')');\n            $parenthesisCounter += $numOpeningParenthesis - $numClosingParenthesis;\n\n            $expressionPieces[] = $pieceOfExpression;\n\n            if ($parenthesisCounter === 0) {\n                break;\n            }\n        }\n        $expression = implode(' ', $expressionPieces);\n        if (Str::startsWith($expression, '((') && Str::endsWith($expression, '))')) {\n            $expression = substr($expression, 1, strlen($expression) - 2);\n        }\n\n        $finalPiece = $this->consume();\n        if ($finalPiece !== null && strtoupper($finalPiece) === 'STORED') {\n            $this->definition->setStoredAs($expression)->setNullable(false);\n        } else {\n            $this->definition->setVirtualAs($expression)->setNullable(false);\n        }\n    }\n\n    private function consumeTimestamp()\n    {\n        $nextPiece = $this->consume();\n        if (strtoupper($nextPiece) === 'ON') {\n            $next = $this->consume();\n            if (strtoupper($next) === 'UPDATE') {\n                $next = $this->consume();\n                if (strtoupper($next) === 'CURRENT_TIMESTAMP') {\n                    $this->definition->setUseCurrentOnUpdate(true);\n                } else {\n                    $this->putBack($next);\n                }\n            } else {\n                $this->putBack($next);\n            }\n        } else {\n            $this->putBack($nextPiece);\n        }\n    }\n\n    //endregion\n\n    //region Resolvers\n    private function resolveColumnMethod()\n    {\n        $mapped = [\n            'int' => 'integer',\n            'tinyint' => 'tinyInteger',\n            'smallint' => 'smallInteger',\n            'mediumint' => 'mediumInteger',\n            'bigint' => 'bigInteger',\n            'varchar' => 'string',\n            'tinytext' => 'string',  //tinytext is not a valid Blueprint method currently\n            'mediumtext' => 'mediumText',\n            'longtext' => 'longText',\n            'blob' => 'binary',\n            'datetime' => 'dateTime',\n            'geometrycollection' => 'geometryCollection',\n            'linestring' => 'lineString',\n            'multilinestring' => 'multiLineString',\n            'multipolygon' => 'multiPolygon',\n            'multipoint' => 'multiPoint',\n        ];\n        if (isset($mapped[$this->columnDataType])) {\n            $this->definition->setMethodName($mapped[$this->columnDataType]);\n        } else {\n            //do some custom resolution\n            $this->definition->setMethodName($this->columnDataType);\n        }\n    }\n\n    private function resolveColumnConstraints(array $constraints)\n    {\n        if ($this->columnDataType === 'char' && count($constraints) === 1 && $constraints[0] == 36) {\n            //uuid for mysql\n            $this->definition->setIsUUID(true);\n\n            return;\n        }\n        if ($this->isArrayType()) {\n            $this->definition->setMethodParameters([array_map(fn ($item) => trim($item, '\\''), $constraints)]);\n        } else {\n            if (Str::contains(strtoupper($this->columnDataType), 'INT')) {\n                $this->definition->setMethodParameters([]); //laravel does not like display field widths\n            } else {\n                if ($this->definition->getMethodName() === 'string') {\n                    if (count($constraints) === 1) {\n                        //has a width set\n                        if ($constraints[0] == Builder::$defaultStringLength) {\n                            $this->definition->setMethodParameters([]);\n\n                            return;\n                        }\n                    }\n                }\n                $this->definition->setMethodParameters(array_map(fn ($item) => (int) $item, $constraints));\n            }\n        }\n    }\n\n    //endregion\n\n    protected function isTextType()\n    {\n        return Str::contains($this->columnDataType, ['char', 'text', 'set', 'enum']);\n    }\n\n    protected function isNumberType()\n    {\n        return Str::contains($this->columnDataType, ['int', 'decimal', 'float', 'double']);\n    }\n\n    protected function isArrayType()\n    {\n        return Str::contains($this->columnDataType, ['enum', 'set']);\n    }\n\n    protected function isBinaryType()\n    {\n        return Str::contains($this->columnDataType, ['bit']);\n    }\n\n    /**\n     * @return mixed\n     */\n    public function getColumnDataType()\n    {\n        return $this->columnDataType;\n    }\n}\n"
  },
  {
    "path": "src/Tokenizers/MySQL/IndexTokenizer.php",
    "content": "<?php\n\nnamespace LaravelMigrationGenerator\\Tokenizers\\MySQL;\n\nuse LaravelMigrationGenerator\\Tokenizers\\BaseIndexTokenizer;\n\nclass IndexTokenizer extends BaseIndexTokenizer\n{\n    public function tokenize(): self\n    {\n        $this->consumeIndexType();\n        if ($this->definition->getIndexType() !== 'primary') {\n            $this->consumeIndexName();\n        }\n\n        if ($this->definition->getIndexType() === 'foreign') {\n            $this->consumeForeignKey();\n        } else {\n            $this->consumeIndexColumns();\n        }\n\n        return $this;\n    }\n\n    private function consumeIndexType()\n    {\n        $piece = $this->consume();\n        $upper = strtoupper($piece);\n        if (in_array($upper, ['PRIMARY', 'UNIQUE', 'FULLTEXT', 'SPATIAL'])) {\n            $this->definition->setIndexType(strtolower($piece));\n            $this->consume(); //just the word KEY\n        } elseif ($upper === 'KEY') {\n            $this->definition->setIndexType('index');\n        } elseif ($upper === 'CONSTRAINT') {\n            $this->definition->setIndexType('foreign');\n        }\n    }\n\n    private function consumeIndexName()\n    {\n        $piece = $this->consume();\n        $this->definition->setIndexName($this->parseColumn($piece));\n    }\n\n    private function consumeIndexColumns()\n    {\n        $piece = $this->consume();\n        $columns = $this->columnsToArray($piece);\n\n        $this->definition->setIndexColumns($columns);\n    }\n\n    private function consumeForeignKey()\n    {\n        $piece = $this->consume();\n        if (strtoupper($piece) === 'FOREIGN') {\n            $this->consume(); //KEY\n\n            $columns = [];\n            $token = $this->consume();\n\n            while (! is_null($token)) {\n                $columns = array_merge($columns, $this->columnsToArray($token));\n                $token = $this->consume();\n                if (strtoupper($token) === 'REFERENCES') {\n                    $this->putBack($token);\n\n                    break;\n                }\n            }\n            $this->definition->setIndexColumns($columns);\n\n            $this->consume(); //REFERENCES\n\n            $referencedTable = $this->parseColumn($this->consume());\n            $this->definition->setForeignReferencedTable($referencedTable);\n\n            $referencedColumns = [];\n            $token = $this->consume();\n            while (! is_null($token)) {\n                $referencedColumns = array_merge($referencedColumns, $this->columnsToArray($token));\n                $token = $this->consume();\n                if (strtoupper($token) === 'ON') {\n                    $this->putBack($token);\n\n                    break;\n                }\n            }\n\n            $this->definition->setForeignReferencedColumns($referencedColumns);\n\n            $this->consumeConstraintActions();\n        } else {\n            $this->putBack($piece);\n        }\n    }\n\n    private function consumeConstraintActions()\n    {\n        while ($token = $this->consume()) {\n            if (strtoupper($token) === 'ON') {\n                $actionType = strtolower($this->consume()); //UPDATE\n                $actionMethod = strtolower($this->consume()); //CASCADE | NO ACTION | SET NULL | SET DEFAULT\n                if ($actionMethod === 'no') {\n                    $this->consume(); //consume ACTION\n                    $actionMethod = 'restrict';\n                } elseif ($actionMethod === 'set') {\n                    $actionMethod = 'set '.$this->consume(); //consume NULL or DEFAULT\n                }\n                $currentActions = $this->definition->getConstraintActions();\n                $currentActions[$actionType] = $actionMethod;\n                $this->definition->setConstraintActions($currentActions);\n            } else {\n                $this->putBack($token);\n\n                break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "stubs/table-create.stub",
    "content": "Schema::create('[TableName]', function (Blueprint $table) {\n    [Schema]\n});"
  },
  {
    "path": "stubs/table-modify.stub",
    "content": "Schema::table('[TableName]', function (Blueprint $table) {\n    [Schema]\n});"
  },
  {
    "path": "stubs/table.stub",
    "content": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nclass Create[TableName:Studly]Table extends Migration\n{\n    /**\n     * Run the migrations.\n     *\n     * @return void\n     */\n    public function up()\n    {\n        [TableUp]\n    }\n\n    /**\n     * Reverse the migrations.\n     *\n     * @return void\n     */\n    public function down()\n    {\n        [TableDown]\n    }\n}\n"
  },
  {
    "path": "stubs/view.stub",
    "content": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nclass Create[ViewName:Studly]View extends Migration\n{\n    /**\n     * Run the migrations.\n     *\n     * @return void\n     */\n    public function up()\n    {\n        DB::statement($this->dropView());\n        DB::statement($this->createView());\n    }\n\n    /**\n     * Reverse the migrations.\n     *\n     * @return void\n     */\n    public function down()\n    {\n        DB::statement($this->dropView());\n    }\n\n    private function createView()\n    {\n        return <<<SQL\n            [Schema]\n        SQL;\n    }\n\n    private function dropView()\n    {\n        return <<<SQL\n            DROP VIEW IF EXISTS `[ViewName]`;\n        SQL;\n    }\n}\n"
  },
  {
    "path": "tests/TestCase.php",
    "content": "<?php\n\nnamespace Tests;\n\nuse LaravelMigrationGenerator\\LaravelMigrationGeneratorProvider;\n\nclass TestCase extends \\Orchestra\\Testbench\\TestCase\n{\n    protected function defineEnvironment($app)\n    {\n        // Setup default database to use sqlite :memory:\n        $app['config']->set('database.default', 'testbench');\n        $app['config']->set('database.connections.testbench', [\n            'driver' => 'sqlite',\n            'database' => ':memory:',\n            'prefix' => '',\n        ]);\n    }\n\n    protected function getPackageProviders($app)\n    {\n        return [\n            LaravelMigrationGeneratorProvider::class,\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Unit/ColumnDefinitionTest.php",
    "content": "<?php\n\nnamespace Tests\\Unit;\n\nuse LaravelMigrationGenerator\\Definitions\\ColumnDefinition;\nuse LaravelMigrationGenerator\\Definitions\\IndexDefinition;\nuse Tests\\TestCase;\n\nclass ColumnDefinitionTest extends TestCase\n{\n    public function test_it_can_add_index_definitions()\n    {\n        $columnDefinition = (new ColumnDefinition())->setIndex(true)->setColumnName('testing')->setMethodName('string');\n        $indexDefinition = (new IndexDefinition())->setIndexName('test')->setIndexType('index');\n        $columnDefinition->addIndexDefinition($indexDefinition);\n\n        $this->assertEquals('$table->string(\\'testing\\')->index(\\'test\\')', $columnDefinition->render());\n    }\n\n    public function test_it_prunes_empty_primary_key_index()\n    {\n        $columnDefinition = (new ColumnDefinition())\n            ->setPrimary(true)\n            ->setColumnName('testing')\n            ->setUnsigned(true)\n            ->setMethodName('integer');\n        $indexDefinition = (new IndexDefinition())\n            ->setIndexType('primary');\n        $columnDefinition->addIndexDefinition($indexDefinition);\n\n        $this->assertEquals('$table->unsignedInteger(\\'testing\\')->primary()', $columnDefinition->render());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/DependencyResolverTest.php",
    "content": "<?php\n\nnamespace Tests\\Unit;\n\nuse LaravelMigrationGenerator\\Definitions\\ColumnDefinition;\nuse LaravelMigrationGenerator\\Definitions\\IndexDefinition;\nuse LaravelMigrationGenerator\\Definitions\\TableDefinition;\nuse LaravelMigrationGenerator\\Helpers\\DependencyResolver;\nuse Tests\\TestCase;\n\nclass DependencyResolverTest extends TestCase\n{\n    public function test_it_can_find_simple_dependencies()\n    {\n        $tableDefinition = new TableDefinition([\n            'tableName' => 'tests',\n            'columnDefinitions' => [\n                (new ColumnDefinition())->setColumnName('id')->setMethodName('id')->setAutoIncrementing(true)->setPrimary(true),\n                (new ColumnDefinition())->setColumnName('name')->setMethodName('string')->setNullable(false),\n            ],\n            'indexDefinitions' => [],\n        ]);\n\n        $foreignTableDefinition = new TableDefinition([\n            'tableName' => 'test_items',\n            'columnDefinitions' => [\n                (new ColumnDefinition())->setColumnName('id')->setMethodName('id')->setAutoIncrementing(true)->setPrimary(true),\n                (new ColumnDefinition())->setColumnName('test_id')->setMethodName('bigInteger')->setNullable(false)->setUnsigned(true),\n            ],\n            'indexDefinitions' => [\n                (new IndexDefinition())->setIndexName('fk_test_id')->setIndexType('foreign')->setForeignReferencedColumns(['id'])->setForeignReferencedTable('tests'),\n            ],\n        ]);\n\n        $resolver = new DependencyResolver([$tableDefinition, $foreignTableDefinition]);\n        $order = $resolver->getDependencyOrder();\n        $this->assertCount(2, $order);\n        $this->assertEquals('tests', $order[0]->getTableName());\n        $this->assertEquals('test_items', $order[1]->getTableName());\n    }\n\n    public function test_it_finds_cyclical_dependencies()\n    {\n        $tableDefinition = new TableDefinition([\n            'tableName' => 'tests',\n            'driver' => 'mysql',\n            'columnDefinitions' => [\n                (new ColumnDefinition())->setColumnName('id')->setMethodName('id')->setAutoIncrementing(true)->setPrimary(true),\n                (new ColumnDefinition())->setColumnName('test_item_id')->setMethodName('bigInteger')->setNullable(false)->setUnsigned(true),\n            ],\n            'indexDefinitions' => [\n                (new IndexDefinition())->setIndexName('fk_test_item_id')->setIndexColumns(['test_item_id'])->setIndexType('foreign')->setForeignReferencedColumns(['id'])->setForeignReferencedTable('test_items'),\n            ],\n        ]);\n\n        $foreignTableDefinition = new TableDefinition([\n            'tableName' => 'test_items',\n            'driver' => 'mysql',\n            'columnDefinitions' => [\n                (new ColumnDefinition())->setColumnName('id')->setMethodName('id')->setAutoIncrementing(true)->setPrimary(true),\n                (new ColumnDefinition())->setColumnName('test_id')->setMethodName('bigInteger')->setNullable(false)->setUnsigned(true),\n            ],\n            'indexDefinitions' => [\n                (new IndexDefinition())->setIndexName('fk_test_id')->setIndexColumns(['test_id'])->setIndexType('foreign')->setForeignReferencedColumns(['id'])->setForeignReferencedTable('tests'),\n            ],\n        ]);\n\n        $resolver = new DependencyResolver([$tableDefinition, $foreignTableDefinition]);\n\n        $order = $resolver->getDependencyOrder();\n        $this->assertCount(4, $order);\n        $this->assertEquals('$table->foreign(\\'test_id\\', \\'fk_test_id\\')->references(\\'id\\')->on(\\'tests\\');', $order[2]->formatter()->getSchema());\n        $this->assertEquals('$table->foreign(\\'test_item_id\\', \\'fk_test_item_id\\')->references(\\'id\\')->on(\\'test_items\\');', $order[3]->formatter()->getSchema());\n    }\n\n    public function test_it_finds_self_referential_dependencies()\n    {\n        $tableDefinition = new TableDefinition([\n            'tableName' => 'tests',\n            'driver' => 'mysql',\n            'columnDefinitions' => [\n                (new ColumnDefinition())->setColumnName('id')->setMethodName('id')->setAutoIncrementing(true)->setPrimary(true),\n                (new ColumnDefinition())->setColumnName('parent_id')->setMethodName('integer')->setUnsigned(true)->setNullable(false),\n            ],\n            'indexDefinitions' => [\n                (new IndexDefinition())->setIndexName('fk_parent_id')->setIndexColumns(['parent_id'])->setIndexType('foreign')->setForeignReferencedColumns(['id'])->setForeignReferencedTable('tests'),\n            ],\n        ]);\n\n        $resolver = new DependencyResolver([$tableDefinition]);\n\n        $order = $resolver->getDependencyOrder();\n        $this->assertCount(1, $order);\n        $this->assertCount(2, $order[0]->getColumnDefinitions());\n        $this->assertCount(1, $order[0]->getIndexDefinitions());\n\n    }\n}\n"
  },
  {
    "path": "tests/Unit/FormatterTest.php",
    "content": "<?php\n\nnamespace Tests\\Unit;\n\nuse LaravelMigrationGenerator\\Helpers\\Formatter;\nuse Tests\\TestCase;\n\nclass FormatterTest extends TestCase\n{\n    public function test_can_format_single_line()\n    {\n        $formatter = new Formatter();\n        $formatter->line('Test');\n        $this->assertEquals('Test', $formatter->render());\n    }\n\n    public function test_can_chain()\n    {\n        $formatter = new Formatter();\n        $line = $formatter->line('$this->call(function(){');\n        $line('$this->die();');\n        $formatter->line('});');\n        $this->assertEquals(<<<'STR'\n        $this->call(function(){\n            $this->die();\n        });\n        STR, $formatter->render());\n    }\n\n    public function test_can_get_current_line_indent_level()\n    {\n        $formatter = new Formatter();\n        $formatter->line('Line');\n        $formatter->line('Line 2');\n\n        $body = <<<'STR'\n    [Test]\nSTR;\n\n        $replaced = $formatter->replaceOnLine('[Test]', $body);\n        $shouldEqual = <<<'STR'\n    Line\n    Line 2\nSTR;\n        $this->assertEquals($shouldEqual, $replaced);\n    }\n\n    public function test_can_replace_on_no_indent()\n    {\n        $replaced = Formatter::replace('    ', '[TEST]', 'Test', '[TEST]');\n        $this->assertEquals('Test', $replaced);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/GeneratorManagers/MySQLGeneratorManagerTest.php",
    "content": "<?php\n\nnamespace Tests\\Unit\\GeneratorManagers;\n\nuse Illuminate\\Support\\Facades\\DB;\nuse LaravelMigrationGenerator\\Definitions\\ColumnDefinition;\nuse LaravelMigrationGenerator\\Definitions\\IndexDefinition;\nuse LaravelMigrationGenerator\\Definitions\\TableDefinition;\nuse LaravelMigrationGenerator\\GeneratorManagers\\MySQLGeneratorManager;\nuse Mockery\\MockInterface;\nuse Tests\\TestCase;\n\nclass MySQLGeneratorManagerTest extends TestCase\n{\n    protected function getManagerMock(array $tableDefinitions)\n    {\n        return $this->partialMock(MySQLGeneratorManager::class, function (MockInterface $mock) use ($tableDefinitions) {\n            $mock->shouldReceive('init', 'createMissingDirectory', 'writeTableMigrations', 'writeViewMigrations');\n            $mock->shouldReceive('createMissingDirectory');\n\n            $mock->shouldReceive('getTableDefinitions')->andReturn($tableDefinitions);\n        });\n    }\n\n    public function test_can_sort_tables()\n    {\n        /** @var MySQLGeneratorManager $mocked */\n        $mocked = $this->getManagerMock([\n            new TableDefinition([\n                'tableName' => 'tests',\n                'driver' => 'mysql',\n                'columnDefinitions' => [\n                    (new ColumnDefinition())->setColumnName('id')->setMethodName('id')->setAutoIncrementing(true)->setPrimary(true),\n                    (new ColumnDefinition())->setColumnName('test_item_id')->setMethodName('bigInteger')->setNullable(false)->setUnsigned(true),\n                ],\n                'indexDefinitions' => [\n                    (new IndexDefinition())->setIndexName('fk_test_item_id')->setIndexColumns(['test_item_id'])->setIndexType('foreign')->setForeignReferencedColumns(['id'])->setForeignReferencedTable('test_items'),\n                ],\n            ]),\n            new TableDefinition([\n                'tableName' => 'test_items',\n                'driver' => 'mysql',\n                'columnDefinitions' => [\n                    (new ColumnDefinition())->setColumnName('id')->setMethodName('id')->setAutoIncrementing(true)->setPrimary(true),\n                    (new ColumnDefinition())->setColumnName('test_id')->setMethodName('bigInteger')->setNullable(false)->setUnsigned(true),\n                ],\n                'indexDefinitions' => [\n                    (new IndexDefinition())->setIndexName('fk_test_id')->setIndexColumns(['test_id'])->setIndexType('foreign')->setForeignReferencedColumns(['id'])->setForeignReferencedTable('tests'),\n                ],\n            ]),\n        ]);\n        $sorted = $mocked->sortTables($mocked->getTableDefinitions());\n        $this->assertCount(4, $sorted);\n        $this->assertStringContainsString('$table->dropForeign', $sorted[3]->formatter()->stubTableDown());\n    }\n\n    public function test_can_remove_database_prefix()\n    {\n        $connection = DB::getDefaultConnection();\n        config()->set('database.connections.'.$connection.'.prefix', 'wp_');\n\n        $mocked = $this->partialMock(MySQLGeneratorManager::class, function (MockInterface $mock) {\n            $mock->shouldReceive('init');\n        });\n\n        $definition = (new TableDefinition())->setTableName('wp_posts');\n        $mocked->addTableDefinition($definition);\n        $this->assertEquals('posts', $definition->getTableName());\n\n        $definition = (new TableDefinition())->setTableName('posts');\n        $mocked->addTableDefinition($definition);\n        $this->assertEquals('posts', $definition->getTableName());\n\n        config()->set('database.connections.'.$connection.'.prefix', '');\n\n        $definition = (new TableDefinition())->setTableName('wp_posts');\n        $mocked->addTableDefinition($definition);\n        $this->assertEquals('wp_posts', $definition->getTableName());\n\n        $definition = (new TableDefinition())->setTableName('posts');\n        $mocked->addTableDefinition($definition);\n        $this->assertEquals('posts', $definition->getTableName());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Generators/MySQLTableGeneratorTest.php",
    "content": "<?php\n\nnamespace Tests\\Unit\\Generators;\n\nuse Illuminate\\Support\\Facades\\Config;\nuse LaravelMigrationGenerator\\Generators\\MySQL\\TableGenerator;\nuse Tests\\TestCase;\n\nclass MySQLTableGeneratorTest extends TestCase\n{\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        $path = __DIR__.'/../../migrations';\n        $this->cleanUpMigrations($path);\n    }\n\n    private function assertSchemaHas($str, $schema)\n    {\n        $this->assertStringContainsString($str, $schema);\n    }\n\n    public function test_runs_correctly()\n    {\n        $generator = TableGenerator::init('table', [\n            '`id` int(9) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY',\n            '`user_id` int(9) unsigned NOT NULL',\n            '`note` varchar(255) NOT NULL',\n            'KEY `fk_user_id_idx` (`user_id`)',\n            'CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE',\n        ]);\n\n        $schema = $generator->definition()->formatter()->getSchema();\n        $this->assertSchemaHas('$table->increments(\\'id\\');', $schema);\n        $this->assertSchemaHas('$table->unsignedInteger(\\'user_id\\');', $schema);\n        $this->assertSchemaHas('$table->string(\\'note\\');', $schema);\n        $this->assertSchemaHas('$table->foreign(\\'user_id\\', \\'fk_user_id\\')->references(\\'id\\')->on(\\'users\\')->onDelete(\\'cascade\\')->onUpdate(\\'cascade\\');', $schema);\n    }\n\n    public function test_self_referential_foreign_key()\n    {\n        $generator = TableGenerator::init('table', [\n            '`id` int(9) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY',\n            '`parent_id` int(9) unsigned NOT NULL',\n            'KEY `fk_parent_id_idx` (`parent_id`)',\n            'CONSTRAINT `fk_parent_id` FOREIGN KEY (`parent_id`) REFERENCES `tables` (`id`) ON DELETE CASCADE ON UPDATE CASCADE',\n        ]);\n\n        $schema = $generator->definition()->formatter()->getSchema();\n        $this->assertSchemaHas('$table->increments(\\'id\\');', $schema);\n        $this->assertSchemaHas('$table->unsignedInteger(\\'parent_id\\');', $schema);\n        $this->assertSchemaHas('$table->foreign(\\'parent_id\\', \\'fk_parent_id\\')->references(\\'id\\')->on(\\'tables\\')->onDelete(\\'cascade\\')->onUpdate(\\'cascade\\');', $schema);\n    }\n\n    private function cleanUpMigrations($path)\n    {\n        if (is_dir($path)) {\n            foreach (glob($path.'/*.php') as $file) {\n                unlink($file);\n            }\n            rmdir($path);\n        }\n    }\n\n    public function test_writes()\n    {\n        Config::set('laravel-migration-generator.table_naming_scheme', '0000_00_00_000000_create_[TableName]_table.php');\n        $generator = TableGenerator::init('table', [\n            '`id` int(9) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY',\n            '`user_id` int(9) unsigned NOT NULL',\n            '`note` varchar(255) NOT NULL',\n            'KEY `fk_user_id_idx` (`user_id`)',\n            'CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE',\n        ]);\n\n        $path = __DIR__.'/../../migrations';\n\n        if (! is_dir($path)) {\n            mkdir($path, 0777, true);\n        }\n\n        $generator->definition()->formatter()->write($path);\n\n        $this->assertFileExists($path.'/0000_00_00_000000_create_table_table.php');\n    }\n\n    public function test_cleans_up_regular_morphs()\n    {\n        $generator = TableGenerator::init('table', [\n            '`id` int(9) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY',\n            '`user_id` int(9) unsigned NOT NULL',\n            '`user_type` varchar(255) NOT NULL',\n            '`note` varchar(255) NOT NULL',\n        ]);\n\n        $schema = $generator->definition()->formatter()->getSchema();\n        $this->assertSchemaHas('$table->morphs(\\'user\\');', $schema);\n    }\n\n    public function test_doesnt_clean_up_morph_looking_columns()\n    {\n        $generator = TableGenerator::init('table', [\n            '`id` int(9) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY',\n            '`user_id` varchar(255) NOT NULL',\n            '`user_type` varchar(255) NOT NULL',\n            '`note` varchar(255) NOT NULL',\n        ]);\n\n        $schema = $generator->definition()->formatter()->getSchema();\n        $this->assertStringNotContainsString('$table->morphs(\\'user\\');', $schema);\n    }\n\n    public function test_cleans_up_uuid_morphs()\n    {\n        $generator = TableGenerator::init('table', [\n            '`id` int(9) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY',\n            '`user_id` char(36) NOT NULL',\n            '`user_type` varchar(255) NOT NULL',\n            '`note` varchar(255) NOT NULL',\n        ]);\n\n        $schema = $generator->definition()->formatter()->getSchema();\n        $this->assertSchemaHas('$table->uuidMorphs(\\'user\\');', $schema);\n    }\n\n    public function test_cleans_up_uuid_morphs_nullable()\n    {\n        $generator = TableGenerator::init('table', [\n            '`id` int(9) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY',\n            '`user_id` char(36) DEFAULT NULL',\n            '`user_type` varchar(255) DEFAULT NULL',\n            '`note` varchar(255) NOT NULL',\n        ]);\n\n        $schema = $generator->definition()->formatter()->getSchema();\n        $this->assertSchemaHas('$table->nullableUuidMorphs(\\'user\\');', $schema);\n    }\n\n    public function test_doesnt_clean_non_auto_inc_id_to_laravel_method()\n    {\n        $generator = TableGenerator::init('table', [\n            '`id` int(9) unsigned NOT NULL',\n            'PRIMARY KEY `id`',\n        ]);\n\n        $schema = $generator->definition()->formatter()->getSchema();\n        $this->assertSchemaHas('$table->unsignedInteger(\\'id\\')->primary();', $schema);\n    }\n\n    public function test_does_clean_auto_inc_int_to_laravel_method()\n    {\n        $generator = TableGenerator::init('table', [\n            '`id` int(9) unsigned NOT NULL AUTO_INCREMENT',\n            'PRIMARY KEY `id`',\n        ]);\n\n        $schema = $generator->definition()->formatter()->getSchema();\n        $this->assertSchemaHas('$table->increments(\\'id\\');', $schema);\n    }\n\n    public function test_does_clean_auto_inc_big_int_to_laravel_method()\n    {\n        $generator = TableGenerator::init('table', [\n            '`id` bigint(12) unsigned NOT NULL AUTO_INCREMENT',\n            'PRIMARY KEY `id`',\n        ]);\n\n        $schema = $generator->definition()->formatter()->getSchema();\n        $this->assertSchemaHas('$table->id();', $schema);\n    }\n\n    public function test_doesnt_clean_timestamps_with_use_current()\n    {\n        $generator = TableGenerator::init('table', [\n            'id int auto_increment primary key',\n            'created_at timestamp not null default CURRENT_TIMESTAMP',\n            'updated_at timestamp null on update CURRENT_TIMESTAMP',\n        ]);\n        $schema = $generator->definition()->formatter()->getSchema();\n        $this->assertSchemaHas('$table->timestamp(\\'created_at\\')->useCurrent()', $schema);\n        $this->assertSchemaHas('$table->timestamp(\\'updated_at\\')->nullable()->useCurrentOnUpdate()', $schema);\n    }\n\n    public function test_doesnt_clean_timestamps_with_use_current_on_update()\n    {\n        $generator = TableGenerator::init('table', [\n            'id int auto_increment primary key',\n            'created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',\n            'updated_at timestamp null on update CURRENT_TIMESTAMP',\n        ]);\n        $schema = $generator->definition()->formatter()->getSchema();\n        $this->assertSchemaHas('$table->timestamp(\\'created_at\\')->useCurrent()->useCurrentOnUpdate()', $schema);\n        $this->assertSchemaHas('$table->timestamp(\\'updated_at\\')->nullable()->useCurrentOnUpdate()', $schema);\n    }\n\n    public function test_doesnt_clean_timestamps_with_use_defined_datatype_on_timestamp_configuration()\n    {\n        config()->set('laravel-migration-generator.definitions.use_defined_datatype_on_timestamp', true);\n        $generator = TableGenerator::init('table', [\n            'id int auto_increment primary key',\n            'created_at datetime NOT NULL',\n            'updated_at datetime NOT NULL',\n        ]);\n        $schema = $generator->definition()->formatter()->getSchema();\n        $this->assertSchemaHas('$table->dateTime(\\'created_at\\')', $schema);\n        $this->assertSchemaHas('$table->dateTime(\\'updated_at\\')', $schema);\n    }\n\n    public function test_removes_index_from_column_if_fk()\n    {\n        $generator = TableGenerator::init('test', [\n            '`import_id` bigint(20) unsigned DEFAULT NULL',\n            '`import_service_id` bigint(20) unsigned DEFAULT NULL',\n            'KEY `fk_import_id` (`import_id`)',\n            'KEY `fk_import_service_id` (`import_service_id`)',\n            'CONSTRAINT `fk_import_id` FOREIGN KEY (`import_id`) REFERENCES `imports` (`id`)',\n            'CONSTRAINT `fk_import_service_id` FOREIGN KEY (`import_service_id`) REFERENCES `import_services` (`id`)',\n        ]);\n\n        $schema = $generator->definition()->formatter()->getSchema();\n        $this->assertSchemaHas('$table->unsignedBigInteger(\\'import_id\\')->nullable();', $schema);\n        $this->assertSchemaHas('$table->unsignedBigInteger(\\'import_service_id\\')->nullable();', $schema);\n        $this->assertSchemaHas('$table->foreign(\\'import_id\\', \\'fk_import_id\\')->references(\\'id\\')->on(\\'imports\\');', $schema);\n        $this->assertSchemaHas('$table->foreign(\\'import_service_id\\', \\'fk_import_service_id\\')->references(\\'id\\')->on(\\'import_services\\');', $schema);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Generators/MySQLViewGeneratorTest.php",
    "content": "<?php\n\nnamespace Tests\\Unit\\Generators;\n\nuse LaravelMigrationGenerator\\Generators\\MySQL\\ViewGenerator;\nuse Tests\\TestCase;\n\nclass MySQLViewGeneratorTest extends TestCase\n{\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        $path = __DIR__.'/../../migrations';\n        $this->cleanUpMigrations($path);\n    }\n\n    private function cleanUpMigrations($path)\n    {\n        if (is_dir($path)) {\n            foreach (glob($path.'/*.php') as $file) {\n                unlink($file);\n            }\n            rmdir($path);\n        }\n    }\n\n    public function test_generates()\n    {\n        $generator = ViewGenerator::init('viewName', 'CREATE ALGORITHM=UNDEFINED DEFINER=`homestead`@`%` SQL SECURITY DEFINER VIEW `view_client_config` AS select `cfg`.`client_id` AS `client_id`,(case when (`cfg`.`client_type_can_edit` = 1) then 1 when (isnull(`cfg`.`client_type_can_edit`) and (`cfg`.`default_can_edit` = 1)) then 1 else 0 end) AS `can_edit` from `table` `cfg`');\n\n        $this->assertStringStartsWith('CREATE VIEW `view_client_config` AS', $generator->definition()->getSchema());\n    }\n\n    public function test_writes()\n    {\n        $generator = ViewGenerator::init('viewName', 'CREATE ALGORITHM=UNDEFINED DEFINER=`homestead`@`%` SQL SECURITY DEFINER VIEW `view_client_config` AS select `cfg`.`client_id` AS `client_id`,(case when (`cfg`.`client_type_can_edit` = 1) then 1 when (isnull(`cfg`.`client_type_can_edit`) and (`cfg`.`default_can_edit` = 1)) then 1 else 0 end) AS `can_edit` from `table` `cfg`');\n        $path = __DIR__.'/../../migrations';\n\n        if (! is_dir($path)) {\n            mkdir($path, 0777, true);\n        }\n        $written = $generator->definition()->formatter()->write($path);\n        $this->assertFileExists($written);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Tokenizers/MySQL/ColumnTokenizerTest.php",
    "content": "<?php\n\nnamespace Tests\\Unit\\Tokenizers\\MySQL;\n\nuse LaravelMigrationGenerator\\Tokenizers\\MySQL\\ColumnTokenizer;\nuse Tests\\TestCase;\n\nclass ColumnTokenizerTest extends TestCase\n{\n    //region VARCHAR\n    public function test_it_tokenizes_a_not_null_varchar_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('email', $columnDefinition->getColumnName());\n        $this->assertEquals('varchar', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('string', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertEquals('utf8mb4_unicode_ci', $columnDefinition->getCollation());\n        $this->assertEquals('$table->string(\\'email\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_varchar_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`favorite_color` varchar(200) COLLATE utf8mb4_unicode_ci');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('favorite_color', $columnDefinition->getColumnName());\n        $this->assertEquals('varchar', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('string', $columnDefinition->getMethodName());\n        $this->assertEquals(200, $columnDefinition->getMethodParameters()[0]);\n        $this->assertNull($columnDefinition->isNullable());\n        $this->assertEquals('utf8mb4_unicode_ci', $columnDefinition->getCollation());\n        $this->assertEquals('$table->string(\\'favorite_color\\', 200)', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_varchar_default_value_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`favorite_color` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT \\'orange\\'');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('favorite_color', $columnDefinition->getColumnName());\n        $this->assertEquals('varchar', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('string', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->isNullable());\n        $this->assertEquals('utf8mb4_unicode_ci', $columnDefinition->getCollation());\n        $this->assertEquals('orange', $columnDefinition->getDefaultValue());\n        $this->assertEquals('$table->string(\\'favorite_color\\')->default(\\'orange\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_varchar_default_value_null_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`favorite_color` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('favorite_color', $columnDefinition->getColumnName());\n        $this->assertEquals('varchar', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('string', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertTrue($columnDefinition->isNullable());\n        $this->assertEquals('utf8mb4_unicode_ci', $columnDefinition->getCollation());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertEquals('$table->string(\\'favorite_color\\')->nullable()', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_varchar_default_value_null_column_with_comment_with_setting()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`favorite_color` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT \\'favorite color\\'');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('favorite_color', $columnDefinition->getColumnName());\n        $this->assertEquals('varchar', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('string', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertTrue($columnDefinition->isNullable());\n        $this->assertEquals('utf8mb4_unicode_ci', $columnDefinition->getCollation());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertEquals('favorite color', $columnDefinition->getComment());\n        $this->assertEquals('$table->string(\\'favorite_color\\')->nullable()->comment(\"favorite color\")', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_varchar_default_value_null_column_with_comment_without_setting()\n    {\n        config()->set('laravel-migration-generator.definitions.with_comments', false);\n        $columnTokenizer = ColumnTokenizer::parse('`favorite_color` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT \\'favorite color\\'');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('favorite_color', $columnDefinition->getColumnName());\n        $this->assertEquals('varchar', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('string', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertTrue($columnDefinition->isNullable());\n        $this->assertEquals('utf8mb4_unicode_ci', $columnDefinition->getCollation());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertEquals('favorite color', $columnDefinition->getComment());\n        $this->assertEquals('$table->string(\\'favorite_color\\')->nullable()', $columnDefinition->render());\n\n        config()->set('laravel-migration-generator.definitions.with_comments', true);\n    }\n\n    public function test_it_tokenizes_a_null_varchar_default_value_null_column_with_comment_apostrophe()\n    {\n        $columnTokenizer = ColumnTokenizer::parse(\"`favorite_color` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'favorite color is ''green''\");\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('favorite_color', $columnDefinition->getColumnName());\n        $this->assertEquals('varchar', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('string', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertTrue($columnDefinition->isNullable());\n        $this->assertEquals('utf8mb4_unicode_ci', $columnDefinition->getCollation());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertEquals('favorite color is \\'green\\'', $columnDefinition->getComment());\n        $this->assertEquals('$table->string(\\'favorite_color\\')->nullable()->comment(\"favorite color is \\'green\\'\")', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_varchar_default_value_null_column_with_comment_quotation()\n    {\n        $columnTokenizer = ColumnTokenizer::parse(\"`favorite_color` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'favorite color is \\\"green\\\"\");\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('favorite_color', $columnDefinition->getColumnName());\n        $this->assertEquals('varchar', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('string', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertTrue($columnDefinition->isNullable());\n        $this->assertEquals('utf8mb4_unicode_ci', $columnDefinition->getCollation());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertEquals('favorite color is \"green\"', $columnDefinition->getComment());\n        $this->assertEquals('$table->string(\\'favorite_color\\')->nullable()->comment(\"favorite color is \\\"green\\\"\")', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_varchar_with_default_and_comment()\n    {\n        $columnTokenizer = ColumnTokenizer::parse(\"`testing` varchar(255) DEFAULT 'this is ''it''' COMMENT 'this is the \\\"comment\\\"'\");\n        $columnDefinition = $columnTokenizer->definition();\n        $this->assertEquals('this is \\'it\\'', $columnDefinition->getDefaultValue());\n        $this->assertEquals('this is the \"comment\"', $columnDefinition->getComment());\n    }\n\n    public function test_it_tokenizes_varchar_with_default_empty_string_and_comment()\n    {\n        $columnTokenizer = ColumnTokenizer::parse(\"`testing` varchar(255) DEFAULT '' COMMENT 'this is the \\\"comment\\\"'\");\n        $columnDefinition = $columnTokenizer->definition();\n        $this->assertEquals('', $columnDefinition->getDefaultValue());\n        $this->assertEquals('this is the \"comment\"', $columnDefinition->getComment());\n    }\n\n    public function test_it_tokenizes_varchar_with_default_empty_string_and_comment_with_apostrophe()\n    {\n        $columnTokenizer = ColumnTokenizer::parse(\"`testing` varchar(255) DEFAULT '' COMMENT 'this is the \\\"comment\\\" ''inside single quote''\");\n        $columnDefinition = $columnTokenizer->definition();\n        $this->assertEquals('', $columnDefinition->getDefaultValue());\n        $this->assertEquals('this is the \"comment\" \\'inside single quote\\'', $columnDefinition->getComment());\n    }\n\n    public function test_it_tokenizes_varchar_with_boolean_literal_default()\n    {\n        $columnTokenizer = ColumnTokenizer::parse(\"`testing` bit(2) DEFAULT b'10'\");\n        $columnDefinition = $columnTokenizer->definition();\n        $this->assertEquals('bit', $columnDefinition->getMethodName());\n        $this->assertEquals('b\\'10\\'', $columnDefinition->getDefaultValue());\n        $this->assertEquals(\"\\$table->bit('testing', 2)->default(b'10')\", $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_char_column_with_character_and_collation()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`country` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT \\'US\\'');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('country', $columnDefinition->getColumnName());\n        $this->assertEquals('char', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('char', $columnDefinition->getMethodName());\n        $this->assertCount(1, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->isNullable());\n        $this->assertEquals('utf8mb4_unicode_ci', $columnDefinition->getCollation());\n        $this->assertEquals('utf8mb4', $columnDefinition->getCharacterSet());\n        $this->assertEquals('$table->char(\\'country\\', 2)->default(\\'US\\')', $columnDefinition->render());\n    }\n\n    //endregion\n\n    //region TEXT & Variants\n    public function test_it_tokenizes_a_not_null_tinytext_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`notes` tinytext NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('notes', $columnDefinition->getColumnName());\n        $this->assertEquals('tinytext', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('string', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->string(\\'notes\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_tinytext_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`notes` tinytext');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('notes', $columnDefinition->getColumnName());\n        $this->assertEquals('tinytext', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('string', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertTrue($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->string(\\'notes\\')->nullable()', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_text_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`notes` text NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('notes', $columnDefinition->getColumnName());\n        $this->assertEquals('text', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('text', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->text(\\'notes\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_text_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`notes` text');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('notes', $columnDefinition->getColumnName());\n        $this->assertEquals('text', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('text', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertTrue($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->text(\\'notes\\')->nullable()', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_mediumtext_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`notes` mediumtext NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('notes', $columnDefinition->getColumnName());\n        $this->assertEquals('mediumtext', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('mediumText', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->mediumText(\\'notes\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_mediumtext_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`notes` mediumtext');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('notes', $columnDefinition->getColumnName());\n        $this->assertEquals('mediumtext', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('mediumText', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertTrue($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->mediumText(\\'notes\\')->nullable()', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_longtext_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`notes` longtext NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('notes', $columnDefinition->getColumnName());\n        $this->assertEquals('longtext', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('longText', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->longText(\\'notes\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_longtext_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`notes` longtext');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('notes', $columnDefinition->getColumnName());\n        $this->assertEquals('longtext', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('longText', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertTrue($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->longText(\\'notes\\')->nullable()', $columnDefinition->render());\n    }\n\n    //endregion\n\n    //region INT & Variants\n    public function test_it_tokenizes_a_not_null_smallint_without_param_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`cats` smallint NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('cats', $columnDefinition->getColumnName());\n        $this->assertEquals('smallint', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('smallInteger', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->smallInteger(\\'cats\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_smallint_with_param_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`cats` smallint(6) NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('cats', $columnDefinition->getColumnName());\n        $this->assertEquals('smallint', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('smallInteger', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->smallInteger(\\'cats\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_unsigned_smallint_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`cats` smallint(6) unsigned NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('cats', $columnDefinition->getColumnName());\n        $this->assertEquals('smallint', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('smallInteger', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertTrue($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->unsignedSmallInteger(\\'cats\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_nullable_big_int_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`template_id` bigint(20) unsigned DEFAULT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('template_id', $columnDefinition->getColumnName());\n        $this->assertEquals('bigint', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('bigInteger', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertTrue($columnDefinition->isNullable());\n        $this->assertTrue($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertEquals('$table->unsignedBigInteger(\\'template_id\\')->nullable()', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_primary_auto_inc_int_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`id` int(9) unsigned NOT NULL AUTO_INCREMENT');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('id', $columnDefinition->getColumnName());\n        $this->assertEquals('int', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('integer', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertFalse($columnDefinition->isPrimary());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertTrue($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->unsignedInteger(\\'id\\')', $columnDefinition->render());\n    }\n\n    public function test_definition_config()\n    {\n        config()->set('laravel-migration-generator.definitions.prefer_unsigned_prefix', false);\n        $columnTokenizer = ColumnTokenizer::parse('`column` int(9) unsigned NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n        $this->assertEquals('$table->integer(\\'column\\')->unsigned()', $columnDefinition->render());\n        config()->set('laravel-migration-generator.definitions.prefer_unsigned_prefix', true);\n    }\n\n    //endregion\n\n    //region FLOAT\n    public function test_it_tokenizes_float_without_params_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`parameter` float NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n        $this->assertEquals('parameter', $columnDefinition->getColumnName());\n        $this->assertEquals('float', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('float', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->float(\\'parameter\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_float_with_params_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`parameter` float(4,2) NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n        $this->assertEquals('parameter', $columnDefinition->getColumnName());\n        $this->assertEquals('float', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('float', $columnDefinition->getMethodName());\n        $this->assertCount(2, $columnDefinition->getMethodParameters());\n        $this->assertEquals(4, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEquals(2, $columnDefinition->getMethodParameters()[1]);\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->float(\\'parameter\\', 4, 2)', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_float_null_without_params_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`parameter` float');\n        $columnDefinition = $columnTokenizer->definition();\n        $this->assertEquals('parameter', $columnDefinition->getColumnName());\n        $this->assertEquals('float', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('float', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->float(\\'parameter\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_float_null_with_params_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`parameter` float(4,2)');\n        $columnDefinition = $columnTokenizer->definition();\n        $this->assertEquals('parameter', $columnDefinition->getColumnName());\n        $this->assertEquals('float', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('float', $columnDefinition->getMethodName());\n        $this->assertCount(2, $columnDefinition->getMethodParameters());\n        $this->assertEquals(4, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEquals(2, $columnDefinition->getMethodParameters()[1]);\n        $this->assertNull($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->float(\\'parameter\\', 4, 2)', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_float_without_params_default_value_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`parameter` float NOT NULL DEFAULT 1.00');\n        $columnDefinition = $columnTokenizer->definition();\n        $this->assertEquals('parameter', $columnDefinition->getColumnName());\n        $this->assertEquals('float', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('float', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertEquals(1.0, $columnDefinition->getDefaultValue());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->float(\\'parameter\\')->default(1.00)', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_float_with_params_default_value_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`parameter` float(4,2) NOT NULL DEFAULT 1.00');\n        $columnDefinition = $columnTokenizer->definition();\n        $this->assertEquals('parameter', $columnDefinition->getColumnName());\n        $this->assertEquals('float', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('float', $columnDefinition->getMethodName());\n        $this->assertCount(2, $columnDefinition->getMethodParameters());\n        $this->assertEquals(4, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEquals(2, $columnDefinition->getMethodParameters()[1]);\n        $this->assertEquals(1.00, $columnDefinition->getDefaultValue());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->float(\\'parameter\\', 4, 2)->default(1.00)', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_float_null_without_params_default_value_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`parameter` float DEFAULT 1.0');\n        $columnDefinition = $columnTokenizer->definition();\n        $this->assertEquals('parameter', $columnDefinition->getColumnName());\n        $this->assertEquals('float', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('float', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertEquals(1.0, $columnDefinition->getDefaultValue());\n        $this->assertNull($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->float(\\'parameter\\')->default(1.0)', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_float_null_with_params_default_value_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`parameter` float(4,2) DEFAULT 1.00');\n        $columnDefinition = $columnTokenizer->definition();\n        $this->assertEquals('parameter', $columnDefinition->getColumnName());\n        $this->assertEquals('float', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('float', $columnDefinition->getMethodName());\n        $this->assertCount(2, $columnDefinition->getMethodParameters());\n        $this->assertEquals(4, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEquals(2, $columnDefinition->getMethodParameters()[1]);\n        $this->assertEquals(1.00, $columnDefinition->getDefaultValue());\n        $this->assertNull($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->float(\\'parameter\\', 4, 2)->default(1.00)', $columnDefinition->render());\n    }\n\n    //endregion\n\n    //region DECIMAL\n    public function test_it_tokenizes_a_not_null_decimal_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`amount` decimal(9,2) NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('amount', $columnDefinition->getColumnName());\n        $this->assertEquals('decimal', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('decimal', $columnDefinition->getMethodName());\n        $this->assertCount(2, $columnDefinition->getMethodParameters());\n        $this->assertEquals(9, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEquals(2, $columnDefinition->getMethodParameters()[1]);\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->decimal(\\'amount\\', 9, 2)', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_unsigned_decimal_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`amount` decimal(9,2) unsigned NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('amount', $columnDefinition->getColumnName());\n        $this->assertEquals('decimal', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('decimal', $columnDefinition->getMethodName());\n        $this->assertCount(2, $columnDefinition->getMethodParameters());\n        $this->assertEquals(9, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEquals(2, $columnDefinition->getMethodParameters()[1]);\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertTrue($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->unsignedDecimal(\\'amount\\', 9, 2)', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_decimal_with_default_value_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`amount` decimal(9,2) NOT NULL DEFAULT 1.00');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('amount', $columnDefinition->getColumnName());\n        $this->assertEquals('decimal', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('decimal', $columnDefinition->getMethodName());\n        $this->assertCount(2, $columnDefinition->getMethodParameters());\n        $this->assertEquals(9, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEquals(2, $columnDefinition->getMethodParameters()[1]);\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals(1.0, $columnDefinition->getDefaultValue());\n        $this->assertEquals('$table->decimal(\\'amount\\', 9, 2)->default(1.00)', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_unsigned_decimal_with_default_value_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`amount` decimal(9,2) unsigned NOT NULL DEFAULT 1.00');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('amount', $columnDefinition->getColumnName());\n        $this->assertEquals('decimal', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('decimal', $columnDefinition->getMethodName());\n        $this->assertCount(2, $columnDefinition->getMethodParameters());\n        $this->assertEquals(9, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEquals(2, $columnDefinition->getMethodParameters()[1]);\n        $this->assertEquals(1.0, $columnDefinition->getDefaultValue());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertTrue($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->unsignedDecimal(\\'amount\\', 9, 2)->default(1.00)', $columnDefinition->render());\n    }\n\n    //endregion\n\n    //region DOUBLE\n    public function test_it_tokenizes_a_not_null_double_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`amount` double(9,2) NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('amount', $columnDefinition->getColumnName());\n        $this->assertEquals('double', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('double', $columnDefinition->getMethodName());\n        $this->assertCount(2, $columnDefinition->getMethodParameters());\n        $this->assertEquals(9, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEquals(2, $columnDefinition->getMethodParameters()[1]);\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->double(\\'amount\\', 9, 2)', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_unsigned_double_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`amount` double(9,2) unsigned NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('amount', $columnDefinition->getColumnName());\n        $this->assertEquals('double', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('double', $columnDefinition->getMethodName());\n        $this->assertCount(2, $columnDefinition->getMethodParameters());\n        $this->assertEquals(9, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEquals(2, $columnDefinition->getMethodParameters()[1]);\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertTrue($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->double(\\'amount\\', 9, 2)->unsigned()', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_double_with_default_value_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`amount` double(9,2) NOT NULL DEFAULT 1.00');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('amount', $columnDefinition->getColumnName());\n        $this->assertEquals('double', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('double', $columnDefinition->getMethodName());\n        $this->assertCount(2, $columnDefinition->getMethodParameters());\n        $this->assertEquals(9, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEquals(2, $columnDefinition->getMethodParameters()[1]);\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals(1.00, $columnDefinition->getDefaultValue());\n        $this->assertEquals('$table->double(\\'amount\\', 9, 2)->default(1.00)', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_unsigned_double_with_default_value_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`amount` double(9,2) unsigned NOT NULL DEFAULT 1.00');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('amount', $columnDefinition->getColumnName());\n        $this->assertEquals('double', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('double', $columnDefinition->getMethodName());\n        $this->assertCount(2, $columnDefinition->getMethodParameters());\n        $this->assertEquals(9, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEquals(2, $columnDefinition->getMethodParameters()[1]);\n        $this->assertEquals(1.00, $columnDefinition->getDefaultValue());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertTrue($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->double(\\'amount\\', 9, 2)->unsigned()->default(1.00)', $columnDefinition->render());\n    }\n\n    //endregion\n\n    //region DATETIME\n    public function test_it_tokenizes_a_not_null_datetime_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`sent_at` datetime NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('sent_at', $columnDefinition->getColumnName());\n        $this->assertEquals('datetime', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('dateTime', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertFalse($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->dateTime(\\'sent_at\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_datetime_default_now_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`sent_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('sent_at', $columnDefinition->getColumnName());\n        $this->assertEquals('datetime', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('dateTime', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertFalse($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->dateTime(\\'sent_at\\')->useCurrent()', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_datetime_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`sent_at` datetime');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('sent_at', $columnDefinition->getColumnName());\n        $this->assertEquals('datetime', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('dateTime', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertNull($columnDefinition->isNullable());\n        $this->assertFalse($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->dateTime(\\'sent_at\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_default_value_datetime_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`sent_at` datetime DEFAULT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('sent_at', $columnDefinition->getColumnName());\n        $this->assertEquals('datetime', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('dateTime', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertTrue($columnDefinition->isNullable());\n        $this->assertFalse($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->dateTime(\\'sent_at\\')->nullable()', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_default_value_now_datetime_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`sent_at` datetime DEFAULT CURRENT_TIMESTAMP');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('sent_at', $columnDefinition->getColumnName());\n        $this->assertEquals('datetime', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('dateTime', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertNull($columnDefinition->isNullable());\n        $this->assertFalse($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->dateTime(\\'sent_at\\')->useCurrent()', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_default_value_now_and_on_update_datetime_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`sent_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('sent_at', $columnDefinition->getColumnName());\n        $this->assertEquals('datetime', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('dateTime', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertNull($columnDefinition->isNullable());\n        $this->assertFalse($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->dateTime(\\'sent_at\\')->useCurrent()->useCurrentOnUpdate()', $columnDefinition->render());\n    }\n\n    //endregion\n\n    //region TIMESTAMP\n    public function test_it_tokenizes_a_not_null_timestamp_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`sent_at` timestamp NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('sent_at', $columnDefinition->getColumnName());\n        $this->assertEquals('timestamp', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('timestamp', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertFalse($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->timestamp(\\'sent_at\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_timestamp_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`sent_at` timestamp');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('sent_at', $columnDefinition->getColumnName());\n        $this->assertEquals('timestamp', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('timestamp', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertNull($columnDefinition->isNullable());\n        $this->assertFalse($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->timestamp(\\'sent_at\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_use_current_timestamp_timestamp_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`sent_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('sent_at', $columnDefinition->getColumnName());\n        $this->assertEquals('timestamp', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('timestamp', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertFalse($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->timestamp(\\'sent_at\\')->useCurrent()', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_not_null_default_value_timestamp_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`sent_at` timestamp NOT NULL DEFAULT \\'2000-01-01 00:00:01\\'');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('sent_at', $columnDefinition->getColumnName());\n        $this->assertEquals('timestamp', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('timestamp', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertEquals('2000-01-01 00:00:01', $columnDefinition->getDefaultValue());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertFalse($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->timestamp(\\'sent_at\\')->default(\\'2000-01-01 00:00:01\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_a_null_default_value_timestamp_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`sent_at` timestamp NULL DEFAULT \\'2000-01-01 00:00:01\\'');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('sent_at', $columnDefinition->getColumnName());\n        $this->assertEquals('timestamp', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('timestamp', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertEquals('2000-01-01 00:00:01', $columnDefinition->getDefaultValue());\n        $this->assertTrue($columnDefinition->isNullable());\n        $this->assertFalse($columnDefinition->isUnsigned());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->timestamp(\\'sent_at\\')->nullable()->default(\\'2000-01-01 00:00:01\\')', $columnDefinition->render());\n    }\n\n    //endregion\n\n    //region ENUM\n    public function test_it_tokenizes_enum_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`status_flag` enum(\\'1\\',\\'2\\',\\'3\\',\\'4\\')');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('status_flag', $columnDefinition->getColumnName());\n        $this->assertEquals('enum', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('enum', $columnDefinition->getMethodName());\n        $this->assertCount(4, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEqualsCanonicalizing([1, 2, 3, 4], $columnDefinition->getMethodParameters()[0]);\n        $this->assertNull($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->enum(\\'status_flag\\', [\\'1\\', \\'2\\', \\'3\\', \\'4\\'])', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_enum_column_with_upper_case_values()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`film_rating` enum(\\'G\\',\\'PG\\',\\'PG-13\\',\\'R\\',\\'NC-17\\')');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('film_rating', $columnDefinition->getColumnName());\n        $this->assertEquals('enum', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('enum', $columnDefinition->getMethodName());\n        $this->assertCount(5, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEqualsCanonicalizing(['G', 'PG', 'PG-13', 'R', 'NC-17'], $columnDefinition->getMethodParameters()[0]);\n        $this->assertNull($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->enum(\\'film_rating\\', [\\'G\\', \\'PG\\', \\'PG-13\\', \\'R\\', \\'NC-17\\'])', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_not_null_enum_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`status_flag` enum(\\'1\\',\\'2\\',\\'3\\',\\'4\\') NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('status_flag', $columnDefinition->getColumnName());\n        $this->assertEquals('enum', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('enum', $columnDefinition->getMethodName());\n        $this->assertCount(4, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEqualsCanonicalizing([1, 2, 3, 4], $columnDefinition->getMethodParameters()[0]);\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->enum(\\'status_flag\\', [\\'1\\', \\'2\\', \\'3\\', \\'4\\'])', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_enum_with_default_value_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`status_flag` enum(\\'1\\',\\'2\\',\\'3\\',\\'4\\') DEFAULT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('status_flag', $columnDefinition->getColumnName());\n        $this->assertEquals('enum', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('enum', $columnDefinition->getMethodName());\n        $this->assertCount(4, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEqualsCanonicalizing([1, 2, 3, 4], $columnDefinition->getMethodParameters()[0]);\n        $this->assertNull($columnDefinition->getDefaultValue());\n        $this->assertTrue($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->enum(\\'status_flag\\', [\\'1\\', \\'2\\', \\'3\\', \\'4\\'])->nullable()', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_not_null_enum_with_default_value_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`status_flag` enum(\\'1\\',\\'2\\',\\'3\\',\\'4\\') NOT NULL DEFAULT \\'1\\'');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('status_flag', $columnDefinition->getColumnName());\n        $this->assertEquals('enum', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('enum', $columnDefinition->getMethodName());\n        $this->assertCount(4, $columnDefinition->getMethodParameters()[0]);\n        $this->assertEqualsCanonicalizing([1, 2, 3, 4], $columnDefinition->getMethodParameters()[0]);\n        $this->assertEquals('1', $columnDefinition->getDefaultValue());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertEquals('$table->enum(\\'status_flag\\', [\\'1\\', \\'2\\', \\'3\\', \\'4\\'])->default(\\'1\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_enum_with_spaces()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`calculate` enum(\\'one\\',\\'and\\',\\'highest or\\',\\'lowest or\\',\\'sum\\',\\'highest position or\\',\\'lowest position or\\') COLLATE utf8mb4_general_ci NOT NULL COMMENT \\'set the way we calculate a feature value. with high or low or the sort is by position\\'');\n        $definition = $columnTokenizer->definition();\n\n        $this->assertEquals('$table->enum(\\'calculate\\', [\\'one\\', \\'and\\', \\'highest or\\', \\'lowest or\\', \\'sum\\', \\'highest position or\\', \\'lowest position or\\'])->comment(\"set the way we calculate a feature value. with high or low or the sort is by position\")', $definition->render());\n    }\n\n    public function test_it_tokenizes_enum_with_special_characters()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`calculate` enum(\\'one\\',\\'and\\',\\'highest-or\\',\\'lowest^or\\',\\'sum%\\',\\'highest $ position or\\',\\'lowest+_<>?/ position or\\',\\'\"quoted\"\\') COLLATE utf8mb4_general_ci NOT NULL COMMENT \\'set the way we calculate a feature value. with high or low or the sort is by position\\'');\n        $definition = $columnTokenizer->definition();\n\n        $this->assertEquals('$table->enum(\\'calculate\\', [\\'one\\', \\'and\\', \\'highest-or\\', \\'lowest^or\\', \\'sum%\\', \\'highest $ position or\\', \\'lowest+_<>?/ position or\\', \\'\"quoted\"\\'])->comment(\"set the way we calculate a feature value. with high or low or the sort is by position\")', $definition->render());\n    }\n\n    public function test_it_tokenizes_enum_with_empty_string()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`text` enum(\\'\\',\\'not-empty-string\\',\\'string with spaces\\') DEFAULT \\'\\'');\n        $definition = $columnTokenizer->definition();\n\n        $this->assertEquals('$table->enum(\\'text\\', [\\'\\', \\'not-empty-string\\', \\'string with spaces\\'])->default(\\'\\')', $definition->render());\n    }\n\n    //endregion\n\n    //region POINT, MULTIPOINT\n    public function test_it_tokenizes_point_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`point` point NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('point', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('point', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertEquals('$table->point(\\'point\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_multipoint_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`point` multipoint NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('multipoint', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('multiPoint', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertEquals('$table->multiPoint(\\'point\\')', $columnDefinition->render());\n    }\n\n    //endregion\n\n    //region POLYGON, MULTIPOLYGON\n    public function test_it_tokenizes_polygon_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`polygon` polygon NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('polygon', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('polygon', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertEquals('$table->polygon(\\'polygon\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_multipolygon_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`polygon` multipolygon NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('multipolygon', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('multiPolygon', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertEquals('$table->multiPolygon(\\'polygon\\')', $columnDefinition->render());\n    }\n\n    //endregion,\n\n    //region GEOMETRY\n    public function test_it_tokenizes_geometry_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`geometry` geometry NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('geometry', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('geometry', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n    }\n\n    public function test_it_tokenizes_geometry_collection_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`geometry_collection` geometrycollection NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('geometrycollection', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('geometryCollection', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n    }\n\n    //endregion\n\n    //region SET\n    public function test_it_tokenizes_set_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`set_field` set(\\'1\\',\\'2\\',\\'3\\') COLLATE utf8mb4_unicode_ci DEFAULT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('set', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('set', $columnDefinition->getMethodName());\n        $this->assertCount(1, $columnDefinition->getMethodParameters());\n        $this->assertNotNull($columnDefinition->getCollation());\n        $this->assertTrue($columnDefinition->isNullable());\n\n        $this->assertEquals('$table->set(\\'set_field\\', [\\'1\\', \\'2\\', \\'3\\'])->nullable()', $columnDefinition->render());\n    }\n\n    //endregion\n\n    //region UUID\n    public function test_it_tokenizes_uuid_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`uuid_col` char(36) COLLATE utf8mb4_unicode_ci NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('char', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('char', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNotNull($columnDefinition->getCollation());\n        $this->assertFalse($columnDefinition->isNullable());\n\n        $this->assertEquals('$table->uuid(\\'uuid_col\\')', $columnDefinition->render());\n    }\n\n    //endregion\n\n    //region DATE, YEAR, TIME\n    public function test_it_tokenizes_date_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`birth_date` date NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('date', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('date', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertFalse($columnDefinition->isNullable());\n\n        $this->assertEquals('$table->date(\\'birth_date\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_year_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`birth_year` year NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('year', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('year', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertFalse($columnDefinition->isNullable());\n\n        $this->assertEquals('$table->year(\\'birth_year\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_time_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`birth_time` time NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('time', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('time', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertFalse($columnDefinition->isNullable());\n\n        $this->assertEquals('$table->time(\\'birth_time\\')', $columnDefinition->render());\n    }\n\n    //endregion\n\n    //region LINESTRING, MULTILINESTRING\n    public function test_it_tokenizes_linestring_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`str` linestring NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('linestring', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('lineString', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertFalse($columnDefinition->isNullable());\n\n        $this->assertEquals('$table->lineString(\\'str\\')', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_multilinestring_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`str` multilinestring NOT NULL');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('multilinestring', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('multiLineString', $columnDefinition->getMethodName());\n        $this->assertCount(0, $columnDefinition->getMethodParameters());\n        $this->assertNull($columnDefinition->getCollation());\n        $this->assertFalse($columnDefinition->isNullable());\n\n        $this->assertEquals('$table->multiLineString(\\'str\\')', $columnDefinition->render());\n    }\n\n    //endregion\n\n    public function test_it_tokenizes_generated_as_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`total` decimal(24,6) GENERATED ALWAYS AS ((`quantity` * `unit_price`)) STORED');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('decimal', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('decimal', $columnDefinition->getMethodName());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertEquals('(`quantity` * `unit_price`)', $columnDefinition->getStoredAs());\n\n        $this->assertEquals('$table->decimal(\\'total\\', 24, 6)->storedAs(\"(`quantity` * `unit_price`)\")', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_generated_as_column_example()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`full_name` varchar(150) COLLATE utf8mb4_unicode_ci GENERATED ALWAYS AS (concat(`first_name`,\\' \\',`last_name`)) STORED');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('varchar', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('string', $columnDefinition->getMethodName());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertEquals('(concat(`first_name`,\\' \\',`last_name`))', $columnDefinition->getStoredAs());\n\n        $this->assertEquals('$table->string(\\'full_name\\', 150)->storedAs(\"(concat(`first_name`,\\' \\',`last_name`))\")', $columnDefinition->render());\n    }\n\n    public function test_it_tokenizes_virtual_as_column()\n    {\n        $columnTokenizer = ColumnTokenizer::parse('`total` decimal(24,6) AS ((`quantity` * `unit_price`))');\n        $columnDefinition = $columnTokenizer->definition();\n\n        $this->assertEquals('decimal', $columnTokenizer->getColumnDataType());\n        $this->assertEquals('decimal', $columnDefinition->getMethodName());\n        $this->assertFalse($columnDefinition->isNullable());\n        $this->assertEquals('(`quantity` * `unit_price`)', $columnDefinition->getVirtualAs());\n\n        $this->assertEquals('$table->decimal(\\'total\\', 24, 6)->virtualAs(\"(`quantity` * `unit_price`)\")', $columnDefinition->render());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Tokenizers/MySQL/IndexTokenizerTest.php",
    "content": "<?php\n\nnamespace Tests\\Tokenizers\\MySQL;\n\nuse LaravelMigrationGenerator\\Tokenizers\\MySQL\\IndexTokenizer;\nuse Tests\\TestCase;\n\nclass IndexTokenizerTest extends TestCase\n{\n    //region Simple Index\n    public function test_it_tokenizes_simple_index()\n    {\n        $indexTokenizer = IndexTokenizer::parse('KEY `password_resets_email_index` (`email`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('index', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n\n        $this->assertEquals('$table->index([\\'email\\'], \\'password_resets_email_index\\')', $indexDefinition->render());\n    }\n\n    public function test_it_doesnt_use_index_name()\n    {\n        config()->set('laravel-migration-generator.definitions.use_defined_index_names', false);\n        $indexTokenizer = IndexTokenizer::parse('KEY `password_resets_email_index` (`email`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('index', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n\n        $this->assertEquals('$table->index([\\'email\\'])', $indexDefinition->render());\n        config()->set('laravel-migration-generator.definitions.use_defined_index_names', true);\n    }\n\n    //endregion\n\n    //region Primary Key\n    public function test_it_tokenizes_simple_primary_key()\n    {\n        $indexTokenizer = IndexTokenizer::parse('PRIMARY KEY (`id`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('primary', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n\n        $this->assertEquals('$table->primary([\\'id\\'])', $indexDefinition->render());\n    }\n\n    public function test_it_tokenizes_two_column_primary_key()\n    {\n        $indexTokenizer = IndexTokenizer::parse('PRIMARY KEY (`email`,`token`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('primary', $indexDefinition->getIndexType());\n        $this->assertTrue($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(2, $indexDefinition->getIndexColumns());\n        $this->assertEqualsCanonicalizing(['email', 'token'], $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->primary([\\'email\\', \\'token\\'])', $indexDefinition->render());\n    }\n\n    //endregion\n\n    //region Unique Key\n    public function test_it_tokenizes_simple_unique_key()\n    {\n        $indexTokenizer = IndexTokenizer::parse('UNIQUE KEY `users_email_unique` (`email`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('unique', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n\n        $this->assertEquals('$table->unique([\\'email\\'], \\'users_email_unique\\')', $indexDefinition->render());\n    }\n\n    public function test_it_doesnt_use_unique_key_index_name()\n    {\n        config()->set('laravel-migration-generator.definitions.use_defined_unique_key_index_names', false);\n        $indexTokenizer = IndexTokenizer::parse('UNIQUE KEY `users_email_unique` (`email`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('unique', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n\n        $this->assertEquals('$table->unique([\\'email\\'])', $indexDefinition->render());\n        config()->set('laravel-migration-generator.definitions.use_defined_unique_key_index_names', true);\n    }\n\n    public function test_it_tokenizes_two_column_unique_key()\n    {\n        $indexTokenizer = IndexTokenizer::parse('UNIQUE KEY `users_email_location_id_unique` (`email`,`location_id`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('unique', $indexDefinition->getIndexType());\n        $this->assertTrue($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(2, $indexDefinition->getIndexColumns());\n        $this->assertEqualsCanonicalizing(['email', 'location_id'], $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->unique([\\'email\\', \\'location_id\\'], \\'users_email_location_id_unique\\')', $indexDefinition->render());\n    }\n\n    public function test_it_tokenizes_two_column_unique_key_and_doesnt_use_index_name()\n    {\n        config()->set('laravel-migration-generator.definitions.use_defined_unique_key_index_names', false);\n        $indexTokenizer = IndexTokenizer::parse('UNIQUE KEY `users_email_location_id_unique` (`email`,`location_id`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('unique', $indexDefinition->getIndexType());\n        $this->assertTrue($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(2, $indexDefinition->getIndexColumns());\n        $this->assertEqualsCanonicalizing(['email', 'location_id'], $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->unique([\\'email\\', \\'location_id\\'])', $indexDefinition->render());\n        config()->set('laravel-migration-generator.definitions.use_defined_unique_key_index_names', true);\n    }\n\n    //endregion\n\n    //region Foreign Constraints\n    public function test_it_tokenizes_foreign_key()\n    {\n        $indexTokenizer = IndexTokenizer::parse('CONSTRAINT `fk_bank_accounts_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('foreign', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(1, $indexDefinition->getIndexColumns());\n        $this->assertEquals('users', $indexDefinition->getForeignReferencedTable());\n        $this->assertEquals(['id'], $indexDefinition->getForeignReferencedColumns());\n        $this->assertEquals(['user_id'], $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->foreign(\\'user_id\\', \\'fk_bank_accounts_user_id\\')->references(\\'id\\')->on(\\'users\\')', $indexDefinition->render());\n    }\n\n    public function test_it_tokenizes_foreign_key_doesnt_use_index_name()\n    {\n        config()->set('laravel-migration-generator.definitions.use_defined_foreign_key_index_names', false);\n        $indexTokenizer = IndexTokenizer::parse('CONSTRAINT `fk_bank_accounts_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('foreign', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(1, $indexDefinition->getIndexColumns());\n        $this->assertEquals('users', $indexDefinition->getForeignReferencedTable());\n        $this->assertEquals(['id'], $indexDefinition->getForeignReferencedColumns());\n        $this->assertEquals(['user_id'], $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->foreign(\\'user_id\\')->references(\\'id\\')->on(\\'users\\')', $indexDefinition->render());\n        config()->set('laravel-migration-generator.definitions.use_defined_foreign_key_index_names', true);\n    }\n\n    public function test_it_tokenizes_foreign_key_with_update()\n    {\n        $indexTokenizer = IndexTokenizer::parse('CONSTRAINT `fk_bank_accounts_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('foreign', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(1, $indexDefinition->getIndexColumns());\n        $this->assertEquals('users', $indexDefinition->getForeignReferencedTable());\n        $this->assertEquals(['id'], $indexDefinition->getForeignReferencedColumns());\n        $this->assertEquals(['user_id'], $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->foreign(\\'user_id\\', \\'fk_bank_accounts_user_id\\')->references(\\'id\\')->on(\\'users\\')->onUpdate(\\'cascade\\')', $indexDefinition->render());\n    }\n\n    public function test_it_tokenizes_foreign_key_with_delete()\n    {\n        $indexTokenizer = IndexTokenizer::parse('CONSTRAINT `fk_bank_accounts_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('foreign', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(1, $indexDefinition->getIndexColumns());\n        $this->assertEquals('users', $indexDefinition->getForeignReferencedTable());\n        $this->assertEquals(['id'], $indexDefinition->getForeignReferencedColumns());\n        $this->assertEquals(['user_id'], $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->foreign(\\'user_id\\', \\'fk_bank_accounts_user_id\\')->references(\\'id\\')->on(\\'users\\')->onDelete(\\'cascade\\')', $indexDefinition->render());\n    }\n\n    public function test_it_tokenizes_foreign_key_with_update_and_delete()\n    {\n        $indexTokenizer = IndexTokenizer::parse('CONSTRAINT `fk_bank_accounts_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('foreign', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(1, $indexDefinition->getIndexColumns());\n        $this->assertEquals('users', $indexDefinition->getForeignReferencedTable());\n        $this->assertEquals(['id'], $indexDefinition->getForeignReferencedColumns());\n        $this->assertEquals(['user_id'], $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->foreign(\\'user_id\\', \\'fk_bank_accounts_user_id\\')->references(\\'id\\')->on(\\'users\\')->onUpdate(\\'cascade\\')->onDelete(\\'cascade\\')', $indexDefinition->render());\n    }\n\n    public function test_it_tokenizes_foreign_key_with_multiple_columns()\n    {\n        $indexTokenizer = IndexTokenizer::parse('CONSTRAINT `table2_ibfk_1` FOREIGN KEY (`table2-foreign1`, `table2-foreign2`) REFERENCES `table1` (`table1-field1`, `table1-field2`) ON DELETE CASCADE ON UPDATE CASCADE');\n        $definition = $indexTokenizer->definition();\n\n        $this->assertEquals('foreign', $definition->getIndexType());\n        $this->assertTrue($definition->isMultiColumnIndex());\n        $this->assertCount(2, $definition->getIndexColumns());\n        $this->assertEquals('table1', $definition->getForeignReferencedTable());\n        $this->assertSame(['table1-field1', 'table1-field2'], $definition->getForeignReferencedColumns());\n        $this->assertSame(['table2-foreign1', 'table2-foreign2'], $definition->getIndexColumns());\n        $this->assertEquals('$table->foreign([\\'table2-foreign1\\', \\'table2-foreign2\\'], \\'table2_ibfk_1\\')->references([\\'table1-field1\\', \\'table1-field2\\'])->on(\\'table1\\')->onDelete(\\'cascade\\')->onUpdate(\\'cascade\\')', $definition->render());\n    }\n\n    public function test_it_tokenizes_foreign_key_with_update_restrict()\n    {\n        $indexTokenizer = IndexTokenizer::parse('CONSTRAINT `fk_bank_accounts_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE NO ACTION');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('foreign', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(1, $indexDefinition->getIndexColumns());\n        $this->assertEquals('users', $indexDefinition->getForeignReferencedTable());\n        $this->assertEquals(['id'], $indexDefinition->getForeignReferencedColumns());\n        $this->assertEquals(['user_id'], $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->foreign(\\'user_id\\', \\'fk_bank_accounts_user_id\\')->references(\\'id\\')->on(\\'users\\')->onUpdate(\\'restrict\\')', $indexDefinition->render());\n    }\n\n    public function test_it_tokenizes_foreign_key_with_update_set_null()\n    {\n        $indexTokenizer = IndexTokenizer::parse('CONSTRAINT `fk_bank_accounts_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE SET NULL');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('foreign', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(1, $indexDefinition->getIndexColumns());\n        $this->assertEquals('users', $indexDefinition->getForeignReferencedTable());\n        $this->assertEquals(['id'], $indexDefinition->getForeignReferencedColumns());\n        $this->assertEquals(['user_id'], $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->foreign(\\'user_id\\', \\'fk_bank_accounts_user_id\\')->references(\\'id\\')->on(\\'users\\')->onUpdate(\\'set NULL\\')', $indexDefinition->render());\n    }\n\n    public function test_it_tokenizes_foreign_key_with_update_set_default()\n    {\n        $indexTokenizer = IndexTokenizer::parse('CONSTRAINT `fk_bank_accounts_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE SET DEFAULT');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('foreign', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(1, $indexDefinition->getIndexColumns());\n        $this->assertEquals('users', $indexDefinition->getForeignReferencedTable());\n        $this->assertEquals(['id'], $indexDefinition->getForeignReferencedColumns());\n        $this->assertEquals(['user_id'], $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->foreign(\\'user_id\\', \\'fk_bank_accounts_user_id\\')->references(\\'id\\')->on(\\'users\\')->onUpdate(\\'set DEFAULT\\')', $indexDefinition->render());\n    }\n\n    //endregion\n\n    //region Fulltext Index\n    public function test_it_tokenizes_simple_fulltext_index()\n    {\n        $indexTokenizer = IndexTokenizer::parse('FULLTEXT KEY `posts_content_fulltext` (`content`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('fulltext', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n        $this->assertEquals(['content'], $indexDefinition->getIndexColumns());\n        $this->assertEquals('posts_content_fulltext', $indexDefinition->getIndexName());\n\n        $this->assertEquals('$table->fullText([\\'content\\'], \\'posts_content_fulltext\\')', $indexDefinition->render());\n    }\n\n    public function test_it_tokenizes_fulltext_index_without_using_index_name()\n    {\n        config()->set('laravel-migration-generator.definitions.use_defined_index_names', false);\n        $indexTokenizer = IndexTokenizer::parse('FULLTEXT KEY `posts_content_fulltext` (`content`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('fulltext', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n\n        $this->assertEquals('$table->fullText([\\'content\\'])', $indexDefinition->render());\n        config()->set('laravel-migration-generator.definitions.use_defined_index_names', true);\n    }\n\n    public function test_it_tokenizes_multi_column_fulltext_index()\n    {\n        $indexTokenizer = IndexTokenizer::parse('FULLTEXT KEY `posts_title_content_fulltext` (`title`,`content`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('fulltext', $indexDefinition->getIndexType());\n        $this->assertTrue($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(2, $indexDefinition->getIndexColumns());\n        $this->assertEqualsCanonicalizing(['title', 'content'], $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->fullText([\\'title\\', \\'content\\'], \\'posts_title_content_fulltext\\')', $indexDefinition->render());\n    }\n\n    public function test_it_tokenizes_multi_column_fulltext_index_without_using_index_name()\n    {\n        config()->set('laravel-migration-generator.definitions.use_defined_index_names', false);\n        $indexTokenizer = IndexTokenizer::parse('FULLTEXT KEY `posts_title_content_fulltext` (`title`,`content`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('fulltext', $indexDefinition->getIndexType());\n        $this->assertTrue($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(2, $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->fullText([\\'title\\', \\'content\\'])', $indexDefinition->render());\n        config()->set('laravel-migration-generator.definitions.use_defined_index_names', true);\n    }\n\n    //endregion\n\n    //region Spatial Index\n    public function test_it_tokenizes_simple_spatial_index()\n    {\n        $indexTokenizer = IndexTokenizer::parse('SPATIAL KEY `locations_coordinates_spatial` (`coordinates`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('spatial', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n        $this->assertEquals(['coordinates'], $indexDefinition->getIndexColumns());\n        $this->assertEquals('locations_coordinates_spatial', $indexDefinition->getIndexName());\n\n        $this->assertEquals('$table->spatialIndex([\\'coordinates\\'], \\'locations_coordinates_spatial\\')', $indexDefinition->render());\n    }\n\n    public function test_it_tokenizes_spatial_index_without_using_index_name()\n    {\n        config()->set('laravel-migration-generator.definitions.use_defined_index_names', false);\n        $indexTokenizer = IndexTokenizer::parse('SPATIAL KEY `locations_coordinates_spatial` (`coordinates`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('spatial', $indexDefinition->getIndexType());\n        $this->assertFalse($indexDefinition->isMultiColumnIndex());\n\n        $this->assertEquals('$table->spatialIndex([\\'coordinates\\'])', $indexDefinition->render());\n        config()->set('laravel-migration-generator.definitions.use_defined_index_names', true);\n    }\n\n    public function test_it_tokenizes_multi_column_spatial_index()\n    {\n        $indexTokenizer = IndexTokenizer::parse('SPATIAL KEY `locations_lat_lng_spatial` (`latitude`,`longitude`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('spatial', $indexDefinition->getIndexType());\n        $this->assertTrue($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(2, $indexDefinition->getIndexColumns());\n        $this->assertEqualsCanonicalizing(['latitude', 'longitude'], $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->spatialIndex([\\'latitude\\', \\'longitude\\'], \\'locations_lat_lng_spatial\\')', $indexDefinition->render());\n    }\n\n    public function test_it_tokenizes_multi_column_spatial_index_without_using_index_name()\n    {\n        config()->set('laravel-migration-generator.definitions.use_defined_index_names', false);\n        $indexTokenizer = IndexTokenizer::parse('SPATIAL KEY `locations_lat_lng_spatial` (`latitude`,`longitude`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('spatial', $indexDefinition->getIndexType());\n        $this->assertTrue($indexDefinition->isMultiColumnIndex());\n        $this->assertCount(2, $indexDefinition->getIndexColumns());\n\n        $this->assertEquals('$table->spatialIndex([\\'latitude\\', \\'longitude\\'])', $indexDefinition->render());\n        config()->set('laravel-migration-generator.definitions.use_defined_index_names', true);\n    }\n\n    //endregion\n\n    //region Security - Index Name Escaping\n    public function test_it_escapes_single_quotes_in_index_names()\n    {\n        $indexTokenizer = IndexTokenizer::parse('KEY `idx_test\\'s_index` (`email`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('index', $indexDefinition->getIndexType());\n        // The rendered output should escape the single quote to prevent PHP injection\n        $this->assertEquals('$table->index([\\'email\\'], \\'idx_test\\\\\\'s_index\\')', $indexDefinition->render());\n    }\n\n    public function test_it_escapes_single_quotes_in_fulltext_index_names()\n    {\n        $indexTokenizer = IndexTokenizer::parse('FULLTEXT KEY `idx_test\\'s_fulltext` (`content`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('fulltext', $indexDefinition->getIndexType());\n        // The rendered output should escape the single quote to prevent PHP injection\n        $this->assertEquals('$table->fullText([\\'content\\'], \\'idx_test\\\\\\'s_fulltext\\')', $indexDefinition->render());\n    }\n\n    public function test_it_escapes_single_quotes_in_spatial_index_names()\n    {\n        $indexTokenizer = IndexTokenizer::parse('SPATIAL KEY `idx_test\\'s_spatial` (`coordinates`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('spatial', $indexDefinition->getIndexType());\n        // The rendered output should escape the single quote to prevent PHP injection\n        $this->assertEquals('$table->spatialIndex([\\'coordinates\\'], \\'idx_test\\\\\\'s_spatial\\')', $indexDefinition->render());\n    }\n\n    public function test_it_escapes_single_quotes_in_column_names()\n    {\n        $indexTokenizer = IndexTokenizer::parse('KEY `test_index` (`col\\'s_name`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('index', $indexDefinition->getIndexType());\n        // The rendered output should escape the single quote in column name\n        $this->assertEquals('$table->index([\\'col\\\\\\'s_name\\'], \\'test_index\\')', $indexDefinition->render());\n    }\n\n    public function test_it_escapes_backslashes_in_index_names()\n    {\n        $indexTokenizer = IndexTokenizer::parse('KEY `idx\\\\test` (`email`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('index', $indexDefinition->getIndexType());\n        // Backslashes should be escaped\n        $this->assertEquals('$table->index([\\'email\\'], \\'idx\\\\\\\\test\\')', $indexDefinition->render());\n    }\n\n    public function test_it_escapes_backslashes_and_quotes_in_unique_index_names()\n    {\n        $indexTokenizer = IndexTokenizer::parse('UNIQUE KEY `idx\\\\\\'test` (`email`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('unique', $indexDefinition->getIndexType());\n        // Both backslashes and quotes should be escaped\n        $this->assertEquals('$table->unique([\\'email\\'], \\'idx\\\\\\\\\\\\\\'test\\')', $indexDefinition->render());\n    }\n\n    public function test_it_escapes_single_quotes_in_foreign_key_index_names()\n    {\n        config()->set('laravel-migration-generator.definitions.use_defined_foreign_key_index_names', true);\n        $indexTokenizer = IndexTokenizer::parse('CONSTRAINT `fk_test\\'s_key` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)');\n        $indexDefinition = $indexTokenizer->definition();\n\n        $this->assertEquals('foreign', $indexDefinition->getIndexType());\n        // The rendered output should escape the single quote\n        $this->assertEquals('$table->foreign(\\'user_id\\', \\'fk_test\\\\\\'s_key\\')->references(\\'id\\')->on(\\'users\\')', $indexDefinition->render());\n    }\n\n    //endregion\n}\n"
  },
  {
    "path": "tests/Unit/ValueToStringTest.php",
    "content": "<?php\n\nnamespace Tests\\Unit;\n\nuse LaravelMigrationGenerator\\Helpers\\ValueToString;\nuse Tests\\TestCase;\n\nclass ValueToStringTest extends TestCase\n{\n    //region Basic Functionality\n    public function test_it_returns_null_for_null_value()\n    {\n        $this->assertEquals('null', ValueToString::make(null));\n    }\n\n    public function test_it_returns_integer_as_is()\n    {\n        $this->assertEquals(42, ValueToString::make(42));\n    }\n\n    public function test_it_returns_float_as_is()\n    {\n        $this->assertEquals(3.14, ValueToString::make(3.14));\n    }\n\n    public function test_it_quotes_string_value()\n    {\n        $this->assertEquals(\"'hello'\", ValueToString::make('hello'));\n    }\n\n    public function test_it_uses_double_quotes_when_specified()\n    {\n        $this->assertEquals('\"hello\"', ValueToString::make('hello', false, false));\n    }\n\n    public function test_it_returns_array_as_bracketed_list()\n    {\n        $this->assertEquals(\"['one', 'two', 'three']\", ValueToString::make(['one', 'two', 'three']));\n    }\n\n    public function test_it_singles_out_array_when_option_is_true()\n    {\n        $this->assertEquals(\"'single'\", ValueToString::make(['single'], true));\n    }\n\n    public function test_it_does_not_single_out_multi_element_array()\n    {\n        $this->assertEquals(\"['one', 'two']\", ValueToString::make(['one', 'two'], true));\n    }\n\n    //endregion\n\n    //region Escape Functionality\n    public function test_escape_escapes_single_quotes_by_default()\n    {\n        $this->assertEquals(\"test\\\\'s value\", ValueToString::escape(\"test's value\"));\n    }\n\n    public function test_escape_escapes_double_quotes_when_specified()\n    {\n        $this->assertEquals('test\\\\\"s value', ValueToString::escape('test\"s value', false));\n    }\n\n    public function test_escape_escapes_backslashes()\n    {\n        $this->assertEquals('path\\\\\\\\to\\\\\\\\file', ValueToString::escape('path\\\\to\\\\file'));\n    }\n\n    public function test_escape_escapes_both_backslashes_and_single_quotes()\n    {\n        $this->assertEquals(\"it\\\\'s a \\\\\\\\path\", ValueToString::escape(\"it's a \\\\path\"));\n    }\n\n    public function test_escape_escapes_both_backslashes_and_double_quotes()\n    {\n        $this->assertEquals('it\\\\\"s a \\\\\\\\path', ValueToString::escape('it\"s a \\\\path', false));\n    }\n\n    public function test_escape_handles_empty_string()\n    {\n        $this->assertEquals('', ValueToString::escape(''));\n    }\n\n    //endregion\n\n    //region Security Tests - PHP Injection Prevention\n    public function test_make_escapes_single_quotes_in_string()\n    {\n        $malicious = \"test'); phpinfo();//\";\n        $result = ValueToString::make($malicious);\n\n        // The result should have escaped quotes\n        $this->assertEquals(\"'test\\\\'); phpinfo();//'\", $result);\n\n        // Verify it doesn't break out of string context\n        $this->assertStringNotContainsString(\"''\", $result);\n    }\n\n    public function test_make_escapes_single_quotes_in_array()\n    {\n        $malicious = [\"col'); phpinfo();//\"];\n        $result = ValueToString::make($malicious);\n\n        $this->assertEquals(\"['col\\\\'); phpinfo();//']\", $result);\n    }\n\n    public function test_make_escapes_single_quotes_in_singled_out_array()\n    {\n        $malicious = [\"col'); phpinfo();//\"];\n        $result = ValueToString::make($malicious, true);\n\n        $this->assertEquals(\"'col\\\\'); phpinfo();//'\", $result);\n    }\n\n    public function test_make_escapes_backslashes_followed_by_quotes()\n    {\n        // Input: test\\' (backslash followed by single quote)\n        // Output: 'test\\\\\\'' - outer quotes, then \\\\ for backslash, \\' for quote\n        $value = \"test\\\\'\";\n        $result = ValueToString::make($value);\n\n        // 10 characters total: ' t e s t \\\\ \\\\ \\' '\n        $this->assertEquals(10, strlen($result));\n        $this->assertEquals(\"'test\\\\\\\\\\\\''\", $result);\n    }\n\n    public function test_make_escapes_complex_injection_attempt()\n    {\n        $malicious = \"idx\\\\'; system('whoami');//\";\n        $result = ValueToString::make($malicious);\n\n        // Backslashes and quotes should be escaped\n        $this->assertEquals(\"'idx\\\\\\\\\\\\'; system(\\\\'whoami\\\\');//'\", $result);\n    }\n\n    public function test_make_escapes_multiple_quotes_in_array()\n    {\n        $malicious = [\"col'1\", \"col'2\"];\n        $result = ValueToString::make($malicious);\n\n        $this->assertEquals(\"['col\\\\'1', 'col\\\\'2']\", $result);\n    }\n\n    //endregion\n\n    //region Cast Value Tests\n    public function test_cast_float_creates_casted_value()\n    {\n        $result = ValueToString::castFloat('3.14');\n        $this->assertEquals('float$:3.14', $result);\n    }\n\n    public function test_cast_binary_creates_casted_value()\n    {\n        $result = ValueToString::castBinary('0101');\n        $this->assertEquals('binary$:0101', $result);\n    }\n\n    public function test_is_casted_value_detects_float()\n    {\n        $this->assertTrue(ValueToString::isCastedValue('float$:3.14'));\n    }\n\n    public function test_is_casted_value_detects_binary()\n    {\n        $this->assertTrue(ValueToString::isCastedValue('binary$:0101'));\n    }\n\n    public function test_is_casted_value_returns_false_for_regular_string()\n    {\n        $this->assertFalse(ValueToString::isCastedValue('regular string'));\n    }\n\n    public function test_make_handles_casted_float()\n    {\n        $casted = ValueToString::castFloat('3.14');\n        $result = ValueToString::make($casted);\n\n        $this->assertEquals('3.14', $result);\n    }\n\n    public function test_make_handles_casted_binary()\n    {\n        $casted = ValueToString::castBinary('0101');\n        $result = ValueToString::make($casted);\n\n        $this->assertEquals(\"b'0101'\", $result);\n    }\n\n    //endregion\n}\n"
  }
]