Repository: laravel-shift/blueprint (php)

Issue:
The softDeletes should be put after the timestamps 
<!-- PLEASE COMPLETE THIS TEMPLATE -->

- Laravel Version: 8.38.0
- PHP Version: 7.4.16
- Blueprint Version: v1.24.1
- Platform: Windows 

### Issue:

Currently, when I have softDeletes in the yaml, the softDeletes method is put first before the timestamps. Usually 
softDeletes should be put after the timestamps.

Current behavior:

```php
 Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->softDeletes(); // this should be put after the timestamps
            $table->timestamps();
        });
```

Expected behavior:

```php
 Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
            $table->softDeletes(); // this should be put after the timestamps
        });
```

I already tried explicitly putting timestamps and softDeletes order in the yaml but still the same output:
```yaml
models:
  User:
    name: string
    timestamps
    softDeletes

```

Pull request description:
Respect order for soft deletes. Closes #485
Deal with soft deletes in a different way, ensuring migrations respect the order or fields. Closes #485

Patch:
diff --git a/src/Generators/MigrationGenerator.php b/src/Generators/MigrationGenerator.php
index 5ea051a..b72c41c 100644
--- a/src/Generators/MigrationGenerator.php
+++ b/src/Generators/MigrationGenerator.php
@@ -181,6 +181,10 @@ class MigrationGenerator extends AbstractClassGenerator implements Generator
                 $column_definition .= '$table->id(';
             } elseif ($dataType === 'rememberToken') {
                 $column_definition .= '$table->rememberToken(';
+            } elseif ($dataType === 'softDeletes') {
+                $column_definition .= '$table->softDeletes(';
+            } elseif ($dataType === 'softDeletesTz') {
+                $column_definition .= '$table->softDeletesTz(';
             } else {
                 $column_definition .= '$table->' . $dataType . "('{$column->name()}'";
             }
@@ -193,6 +197,7 @@ class MigrationGenerator extends AbstractClassGenerator implements Generator
                     $column_definition .= implode(', ', $column->attributes());
                 }
             }
+
             $column_definition .= ')';
 
             $modifiers = $column->modifiers();
@@ -252,10 +257,6 @@ class MigrationGenerator extends AbstractClassGenerator implements Generator
             $definition .= $column_definition;
         }
 
-        if ($model->usesSoftDeletes()) {
-            $definition .= self::INDENT . '$table->' . $model->softDeletesDataType() . '();' . PHP_EOL;
-        }
-
         if ($model->morphTo()) {
             $definition .= self::INDENT . sprintf('$table->unsignedBigInteger(\'%s\');', Str::lower($model->morphTo() . '_id')) . PHP_EOL;
             $definition .= self::INDENT . sprintf('$table->string(\'%s\');', Str::lower($model->morphTo() . '_type')) . PHP_EOL;
diff --git a/src/Generators/ModelGenerator.php b/src/Generators/ModelGenerator.php
index a8b46f8..d9d11d9 100644
--- a/src/Generators/ModelGenerator.php
+++ b/src/Generators/ModelGenerator.php
@@ -80,17 +80,15 @@ class ModelGenerator extends AbstractClassGenerator implements Generator
                 $phpDoc .= PHP_EOL;
                 $phpDoc .= ' * @property string|null $' . $column->name() . '_type';
                 $phpDoc .= PHP_EOL;
+            } elseif (in_array($column->dataType(), ['softDeletesTz', 'softDeletes'])) {
+                $phpDoc .= ' * @property \Carbon\Carbon $deleted_at';
+                $phpDoc .= PHP_EOL;
             } else {
                 $phpDoc .= sprintf(' * @property %s $%s', $this->phpDataType($column->dataType()), $column->name());
                 $phpDoc .= PHP_EOL;
             }
         }
 
-        if ($model->usesSoftDeletes()) {
-            $phpDoc .= ' * @property \Carbon\Carbon $deleted_at';
-            $phpDoc .= PHP_EOL;
-        }
-
         if ($model->usesTimestamps()) {
             $phpDoc .= ' * @property \Carbon\Carbon $created_at';
             $phpDoc .= PHP_EOL;
@@ -247,6 +245,8 @@ class ModelGenerator extends AbstractClassGenerator implements Generator
                 'created_at',
                 'updated_at',
                 'remember_token',
+                'softdeletes',
+                'softdeletestz',
             ]
         );
     }
diff --git a/src/Lexers/ModelLexer.php b/src/Lexers/ModelLexer.php
index a02f6f1..45fbcda 100644
--- a/src/Lexers/ModelLexer.php
+++ b/src/Lexers/ModelLexer.php
@@ -149,10 +149,8 @@ class ModelLexer implements Lexer
 
         if (isset($columns['softdeletes'])) {
             $model->enableSoftDeletes();
-            unset($columns['softdeletes']);
         } elseif (isset($columns['softdeletestz'])) {
             $model->enableSoftDeletes(true);
-            unset($columns['softdeletestz']);
         }
 
         if (isset($columns['relationships'])) {


Test patch:
diff --git a/src/Generators/TestGenerator.php b/src/Generators/TestGenerator.php
index 2c1032c..fa726db 100644
--- a/src/Generators/TestGenerator.php
+++ b/src/Generators/TestGenerator.php
@@ -226,7 +226,7 @@ class TestGenerator extends AbstractClassGenerator implements Generator
                                 $request_data[$data] = '$' . $variable_name;
                             } elseif (!is_null($local_model)) {
                                 foreach ($local_model->columns() as $local_column) {
-                                    if ($local_column->name() === 'id') {
+                                    if (in_array($local_column->name(), ['id', 'softdeletes', 'softdeletestz'])) {
                                         continue;
                                     }
 
diff --git a/tests/Feature/Generators/MigrationGeneratorTest.php b/tests/Feature/Generators/MigrationGeneratorTest.php
index 5518454..f1e66f5 100644
--- a/tests/Feature/Generators/MigrationGeneratorTest.php
+++ b/tests/Feature/Generators/MigrationGeneratorTest.php
@@ -6,7 +6,6 @@ use Blueprint\Blueprint;
 use Blueprint\Generators\MigrationGenerator;
 use Blueprint\Tree;
 use Carbon\Carbon;
-use Illuminate\Support\Facades\App;
 use Symfony\Component\Finder\SplFileInfo;
 use Tests\TestCase;
 
@@ -53,7 +52,7 @@ class MigrationGeneratorTest extends TestCase
      */
     public function output_writes_migration_for_model_tree($definition, $path, $migration)
     {
-        if ($migration  === 'migrations/return-type-declarations.php') {
+        if ($migration === 'migrations/return-type-declarations.php') {
             $this->app['config']->set('blueprint.use_return_types', true);
         }
 
@@ -85,7 +84,7 @@ class MigrationGeneratorTest extends TestCase
      */
     public function output_updates_migration_for_model_tree($definition, $path, $migration)
     {
-        if ($migration  === 'migrations/return-type-declarations.php') {
+        if ($migration === 'migrations/return-type-declarations.php') {
             $this->app['config']->set('blueprint.use_return_types', true);
         }
 
@@ -608,6 +607,34 @@ class MigrationGeneratorTest extends TestCase
         $this->assertEquals(['created' => [$timestamp_path]], $this->subject->output($tree));
     }
 
+    /**
+     * @test
+     */
+    public function output_respects_softdelete_order()
+    {
+        $this->app->config->set('blueprint.use_constraints', true);
+
+        $this->filesystem->expects('stub')
+            ->with('migration.stub')
+            ->andReturn($this->stub('migration.stub'));
+
+        $now = Carbon::now();
+        Carbon::setTestNow($now);
+
+        $timestamp_path = 'database/migrations/' . $now->format('Y_m_d_His') . '_create_comments_table.php';
+
+        $this->filesystem->expects('exists')
+            ->with($timestamp_path)
+            ->andReturn(false);
+
+        $this->filesystem->expects('put')
+            ->with($timestamp_path, $this->fixture('migrations/soft-deletes-respect-order.php'));
+
+        $tokens = $this->blueprint->parse($this->fixture('drafts/soft-deletes-respect-order.yaml'));
+        $tree = $this->blueprint->analyze($tokens);
+        $this->assertEquals(['created' => [$timestamp_path]], $this->subject->output($tree));
+    }
+
     public function modelTreeDataProvider()
     {
         return [
diff --git a/tests/Feature/Generators/TestGeneratorTest.php b/tests/Feature/Generators/TestGeneratorTest.php
index 37df97d..cb8e82f 100644
--- a/tests/Feature/Generators/TestGeneratorTest.php
+++ b/tests/Feature/Generators/TestGeneratorTest.php
@@ -172,7 +172,6 @@ class TestGeneratorTest extends TestCase
 
         $tokens = $this->blueprint->parse($this->fixture($definition));
         $tree = $this->blueprint->analyze($tokens);
-
         $this->assertEquals(['created' => [$path]], $this->subject->output($tree));
     }
 
@@ -226,7 +225,7 @@ class TestGeneratorTest extends TestCase
                 'tests/Feature/Http/Controllers/TelegramControllerTest.php',
                 'tests/Feature/Http/Controllers/PaymentControllerTest.php',
                 'tests/Feature/Http/Controllers/Api/PaymentControllerTest.php'
-            ],[
+            ], [
                 'tests/call-to-a-member-function-columns-on-null-SubscriptionControllerTest.php',
                 'tests/call-to-a-member-function-columns-on-null-TelegramControllerTest.php',
                 'tests/call-to-a-member-function-columns-on-null-PaymentControllerTest.php',
diff --git a/tests/Feature/Lexers/ModelLexerTest.php b/tests/Feature/Lexers/ModelLexerTest.php
index 4e9be2a..1767bb1 100644
--- a/tests/Feature/Lexers/ModelLexerTest.php
+++ b/tests/Feature/Lexers/ModelLexerTest.php
@@ -377,9 +377,11 @@ class ModelLexerTest extends TestCase
         $this->assertTrue($model->usesSoftDeletes());
 
         $columns = $model->columns();
-        $this->assertCount(1, $columns);
+        $this->assertCount(2, $columns);
         $this->assertEquals('id', $columns['id']->name());
         $this->assertEquals('id', $columns['id']->dataType());
+        $this->assertEquals('softdeletes', $columns['softdeletes']->name());
+        $this->assertEquals('softDeletes', $columns['softdeletes']->dataType());
         $this->assertEquals([], $columns['id']->modifiers());
     }
 
diff --git a/tests/fixtures/drafts/soft-deletes-respect-order.yaml b/tests/fixtures/drafts/soft-deletes-respect-order.yaml
new file mode 100644
index 0000000..078273b
--- /dev/null
+++ b/tests/fixtures/drafts/soft-deletes-respect-order.yaml
@@ -0,0 +1,5 @@
+models:
+  Comment:
+    title: string
+    softdeletes
+    name: string
diff --git a/tests/fixtures/migrations/soft-deletes-respect-order.php b/tests/fixtures/migrations/soft-deletes-respect-order.php
new file mode 100644
index 0000000..ef1036e
--- /dev/null
+++ b/tests/fixtures/migrations/soft-deletes-respect-order.php
@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateCommentsTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('comments', function (Blueprint $table) {
+            $table->id();
+            $table->string('title');
+            $table->softDeletes();
+            $table->string('name');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('comments');
+    }
+}