Repository: laravel-shift/blueprint (php)

Issue:
When using `softdeletes` a validation is added to the Request class
<!-- PLEASE COMPLETE THIS TEMPLATE -->

- Laravel Version: 9.38.0
- PHP Version: 8.1.12
- Blueprint Version: 2.4
- Platform: Mac

### Issue:

This leads to the error: 
> The softdeletes field is required.

```php
class ProjectStoreRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules(): array
    {
        return [
            'title' => ['required', 'string'],
            'softdeletes' => ['required'],
        ];
    }
}
```

### draft.yaml:
```yaml
models:
  Project:
    title: string
    softDeletes
```

Need ignore softDeletesTz and softDeletes in resources and requests
- Laravel Version: 9.14
- PHP Version: 8.1
- Blueprint Version: 2.4
- Platform: Windows

### Issue:
In app/Http/Resources/PostResource.php:
```php
        return [
            'id' => $this->id,
            'title' => $this->title,
            'softdeletestz' => $this->softdeletestz,
        ];
```

In app/Http/Requests/PostStoreRequest.php:
```php
        return [
            'title' => ['required'],
            'softdeletestz' => ['required'],
        ];
```
### draft.yaml:
```yaml
Post:
    title: string:400
    softDeletesTz
```


Pull request:
Remove superfluous `softDeletes` & `softDeletesTz` columns
Resolves #585 and closes #590 

Patch:
diff --git a/src/Generators/MigrationGenerator.php b/src/Generators/MigrationGenerator.php
index b7a8527..6e87f08 100644
--- a/src/Generators/MigrationGenerator.php
+++ b/src/Generators/MigrationGenerator.php
@@ -182,10 +182,6 @@ 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()}'";
             }
@@ -279,6 +275,10 @@ class MigrationGenerator extends AbstractClassGenerator implements Generator
             $definition .= self::INDENT . '$table->' . $model->timestampsDataType() . '();' . PHP_EOL;
         }
 
+        if ($model->usesSoftDeletes()) {
+            $definition .= self::INDENT . '$table->' . $model->softDeletesDataType() . '();' . PHP_EOL;
+        }
+
         return trim($definition);
     }
 
diff --git a/src/Generators/ModelGenerator.php b/src/Generators/ModelGenerator.php
index 2ae65f5..8660aaa 100644
--- a/src/Generators/ModelGenerator.php
+++ b/src/Generators/ModelGenerator.php
@@ -94,9 +94,6 @@ 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;
@@ -110,6 +107,11 @@ class ModelGenerator extends AbstractClassGenerator implements Generator
             $phpDoc .= PHP_EOL;
         }
 
+        if ($model->usesSoftDeletes()) {
+            $phpDoc .= ' * @property \Carbon\Carbon $deleted_at';
+            $phpDoc .= PHP_EOL;
+        }
+
         $phpDoc .= ' */';
 
         return $phpDoc;
diff --git a/src/Lexers/ModelLexer.php b/src/Lexers/ModelLexer.php
index 8f2fcb7..b4b13ab 100644
--- a/src/Lexers/ModelLexer.php
+++ b/src/Lexers/ModelLexer.php
@@ -162,8 +162,10 @@ 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/tests/Feature/Generators/MigrationGeneratorTest.php b/tests/Feature/Generators/MigrationGeneratorTest.php
index 5ca2e67..d5c0826 100644
--- a/tests/Feature/Generators/MigrationGeneratorTest.php
+++ b/tests/Feature/Generators/MigrationGeneratorTest.php
@@ -637,7 +637,7 @@ class MigrationGeneratorTest extends TestCase
     /**
      * @test
      */
-    public function output_respects_softdelete_order()
+    public function output_softdelete_column_last()
     {
         $this->app->config->set('blueprint.use_constraints', true);
 
diff --git a/tests/Feature/Generators/Statements/FormRequestGeneratorTest.php b/tests/Feature/Generators/Statements/FormRequestGeneratorTest.php
index 49ceaf6..1356857 100644
--- a/tests/Feature/Generators/Statements/FormRequestGeneratorTest.php
+++ b/tests/Feature/Generators/Statements/FormRequestGeneratorTest.php
@@ -280,4 +280,68 @@ class FormRequestGeneratorTest extends TestCase
 
         $this->assertEquals(['created' => ['app/Http/Requests/UserStoreRequest.php']], $this->subject->output($tree));
     }
+
+    public function testOutputGeneratesFormRequestWithoutSoftdeletes(): void
+    {
+        $this->filesystem->expects('stub')
+            ->with('request.stub')
+            ->andReturn($this->stub('request.stub'));
+        $this->filesystem->expects('exists')
+            ->twice()
+            ->with('app/Http/Requests')
+            ->andReturnFalse();
+        $this->filesystem->expects('exists')
+            ->with('app/Http/Requests/ProjectStoreRequest.php')
+            ->andReturnFalse();
+        $this->filesystem->expects('exists')
+            ->with('app/Http/Requests/ProjectUpdateRequest.php')
+            ->andReturnFalse();
+        $this->filesystem->expects('makeDirectory')
+            ->twice()
+            ->with('app/Http/Requests', 0755, true);
+        $this->filesystem->expects('put')
+            ->with('app/Http/Requests/ProjectStoreRequest.php', $this->fixture('form-requests/form-requests-softdeletes.php'));
+
+        $tokens = $this->blueprint->parse($this->fixture('drafts/form-requests-softdeletes.yaml'));
+        $tree = $this->blueprint->analyze($tokens);
+
+        self::assertSame([
+            'created' => [
+                'app/Http/Requests/ProjectStoreRequest.php',
+                'app/Http/Requests/ProjectUpdateRequest.php',
+            ],
+        ], $this->subject->output($tree));
+    }
+
+    public function testOutputGeneratesFormRequestWithoutSoftdeletestz(): void
+    {
+        $this->filesystem->expects('stub')
+            ->with('request.stub')
+            ->andReturn($this->stub('request.stub'));
+        $this->filesystem->expects('exists')
+            ->twice()
+            ->with('app/Http/Requests')
+            ->andReturnFalse();
+        $this->filesystem->expects('exists')
+            ->with('app/Http/Requests/RepoStoreRequest.php')
+            ->andReturnFalse();
+        $this->filesystem->expects('exists')
+            ->with('app/Http/Requests/RepoUpdateRequest.php')
+            ->andReturnFalse();
+        $this->filesystem->expects('makeDirectory')
+            ->twice()
+            ->with('app/Http/Requests', 0755, true);
+        $this->filesystem->expects('put')
+            ->with('app/Http/Requests/RepoUpdateRequest.php', $this->fixture('form-requests/form-requests-softdeletestz.php'));
+
+        $tokens = $this->blueprint->parse($this->fixture('drafts/form-requests-softdeletestz.yaml'));
+        $tree = $this->blueprint->analyze($tokens);
+
+        self::assertSame([
+            'created' => [
+                'app/Http/Requests/RepoStoreRequest.php',
+                'app/Http/Requests/RepoUpdateRequest.php',
+            ],
+        ], $this->subject->output($tree));
+    }
 }
diff --git a/tests/Feature/Lexers/ModelLexerTest.php b/tests/Feature/Lexers/ModelLexerTest.php
index da28f48..73c08d7 100644
--- a/tests/Feature/Lexers/ModelLexerTest.php
+++ b/tests/Feature/Lexers/ModelLexerTest.php
@@ -384,11 +384,9 @@ class ModelLexerTest extends TestCase
         $this->assertTrue($model->usesSoftDeletes());
 
         $columns = $model->columns();
-        $this->assertCount(2, $columns);
+        $this->assertCount(1, $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/form-requests-softdeletes.yaml b/tests/fixtures/drafts/form-requests-softdeletes.yaml
new file mode 100644
index 0000000..b3bddd7
--- /dev/null
+++ b/tests/fixtures/drafts/form-requests-softdeletes.yaml
@@ -0,0 +1,8 @@
+models:
+  Project:
+    title: string
+    softDeletes
+
+controllers:
+  Project:
+    resource
diff --git a/tests/fixtures/drafts/form-requests-softdeletestz.yaml b/tests/fixtures/drafts/form-requests-softdeletestz.yaml
new file mode 100644
index 0000000..6556061
--- /dev/null
+++ b/tests/fixtures/drafts/form-requests-softdeletestz.yaml
@@ -0,0 +1,8 @@
+models:
+  Repo:
+    title: string
+    softDeletestz
+
+controllers:
+  Repo:
+    resource
diff --git a/tests/fixtures/form-requests/form-requests-softdeletes.php b/tests/fixtures/form-requests/form-requests-softdeletes.php
new file mode 100644
index 0000000..fbd1ebb
--- /dev/null
+++ b/tests/fixtures/form-requests/form-requests-softdeletes.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class ProjectStoreRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'title' => ['required', 'string'],
+        ];
+    }
+}
diff --git a/tests/fixtures/form-requests/form-requests-softdeletestz.php b/tests/fixtures/form-requests/form-requests-softdeletestz.php
new file mode 100644
index 0000000..6291d9a
--- /dev/null
+++ b/tests/fixtures/form-requests/form-requests-softdeletestz.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class RepoUpdateRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'title' => ['required', 'string'],
+        ];
+    }
+}
diff --git a/tests/fixtures/migrations/soft-deletes-respect-order.php b/tests/fixtures/migrations/soft-deletes-respect-order.php
index 7796571..4148a71 100644
--- a/tests/fixtures/migrations/soft-deletes-respect-order.php
+++ b/tests/fixtures/migrations/soft-deletes-respect-order.php
@@ -16,9 +16,9 @@ return new class extends Migration
         Schema::create('comments', function (Blueprint $table) {
             $table->id();
             $table->string('title');
-            $table->softDeletes();
             $table->string('name');
             $table->timestamps();
+            $table->softDeletes();
         });
     }
 
diff --git a/tests/fixtures/migrations/soft-deletes.php b/tests/fixtures/migrations/soft-deletes.php
index 8b2f1a6..11c6284 100644
--- a/tests/fixtures/migrations/soft-deletes.php
+++ b/tests/fixtures/migrations/soft-deletes.php
@@ -16,8 +16,8 @@ return new class extends Migration
         Schema::create('comments', function (Blueprint $table) {
             $table->id();
             $table->unsignedBigInteger('post_id');
-            $table->softDeletes();
             $table->timestamps();
+            $table->softDeletes();
         });
     }
 
diff --git a/tests/fixtures/migrations/with-path-prefix-table-name-region.php b/tests/fixtures/migrations/with-path-prefix-table-name-region.php
index dbeec66..e35a5d2 100644
--- a/tests/fixtures/migrations/with-path-prefix-table-name-region.php
+++ b/tests/fixtures/migrations/with-path-prefix-table-name-region.php
@@ -16,8 +16,8 @@ return new class extends Migration
         Schema::create('regions', function (Blueprint $table) {
             $table->id();
             $table->string('name_en', 255)->nullable();
-            $table->softDeletes();
             $table->timestamps();
+            $table->softDeletes();
         });
     }
 
diff --git a/tests/fixtures/migrations/with-timezones.php b/tests/fixtures/migrations/with-timezones.php
index b1bfb17..d6c1ec9 100644
--- a/tests/fixtures/migrations/with-timezones.php
+++ b/tests/fixtures/migrations/with-timezones.php
@@ -15,8 +15,8 @@ return new class extends Migration
     {
         Schema::create('comments', function (Blueprint $table) {
             $table->id();
-            $table->softDeletesTz();
             $table->timestampsTz();
+            $table->softDeletesTz();
         });
     }
 
diff --git a/tests/fixtures/models/soft-deletes-phpdoc.php b/tests/fixtures/models/soft-deletes-phpdoc.php
index b2f68ce..97802ab 100644
--- a/tests/fixtures/models/soft-deletes-phpdoc.php
+++ b/tests/fixtures/models/soft-deletes-phpdoc.php
@@ -9,9 +9,9 @@ use Illuminate\Database\Eloquent\SoftDeletes;
 /**
  * @property int $id
  * @property int $post_id
- * @property \Carbon\Carbon $deleted_at
  * @property \Carbon\Carbon $created_at
  * @property \Carbon\Carbon $updated_at
+ * @property \Carbon\Carbon $deleted_at
  */
 class Comment extends Model
 {