Repository: laravel-shift/blueprint (php)

Issue:
Wrong return type on controller, when returning a View
<!-- PLEASE COMPLETE THIS TEMPLATE -->

- Laravel Version: 9.38.0
- PHP Version: 8.1.12
- Blueprint Version: 2.4
- Platform: Mac

### Issue:

I set `'use_return_types' => true,` in `config/blueprint.php`. Blueprint adds the return types but does not take the real return type into account, which in case of a `create()` action is `View`.


```php
class UserController extends Controller
{
    /**
     * @param \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function create(Request $request): \Illuminate\Http\Response
    {
        return view('user.create');
    }
   
    //...    
}
```

### draft.yaml:
```yaml
models:
  User:
    name: string
    email: string unique
    email_verified_at: datetime nullable
    password: string
    remember_token: rememberToken
    softDeletes

controllers:
  User:
    resource
```


Pull request description:
Return correct return-type hints
Resolves #589 

Patch:
diff --git a/src/Generators/ControllerGenerator.php b/src/Generators/ControllerGenerator.php
index 7e82307..ede2b69 100644
--- a/src/Generators/ControllerGenerator.php
+++ b/src/Generators/ControllerGenerator.php
@@ -158,7 +158,16 @@ class ControllerGenerator extends AbstractClassGenerator implements Generator
                 if (isset($fqcn) && $name !== 'destroy' && $controller->isApiResource()) {
                     $method = str_replace(')' . PHP_EOL, '): \\' . $fqcn . PHP_EOL, $method);
                 } else {
-                    $method = str_replace(')' . PHP_EOL, '): \Illuminate\Http\Response' . PHP_EOL, $method);
+                    $returnType = match (true) {
+                        $statement instanceof RenderStatement => 'Illuminate\View\View',
+                        $statement instanceof RedirectStatement => 'Illuminate\Routing\Redirector',
+                        default => 'Illuminate\Http\Response'
+                    };
+
+                    $method = Str::of($method)
+                        ->replace('* @return \\Illuminate\\Http\\Response', '* @return \\' . $returnType)
+                        ->replace(')' . PHP_EOL, '): \\' . $returnType . PHP_EOL)
+                        ->toString();
                 }
             }
 


Test patch:
diff --git a/tests/Feature/BlueprintTest.php b/tests/Feature/BlueprintTest.php
index a52dceb..6562a84 100644
--- a/tests/Feature/BlueprintTest.php
+++ b/tests/Feature/BlueprintTest.php
@@ -662,6 +662,7 @@ class BlueprintTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider namespacesDataProvider
      */
     public function relative_namespace_removes_namespace_prefix_from_reference($namespace, $expected, $reference)
diff --git a/tests/Feature/Generators/ControllerGeneratorTest.php b/tests/Feature/Generators/ControllerGeneratorTest.php
index 0abb1ad..73da327 100644
--- a/tests/Feature/Generators/ControllerGeneratorTest.php
+++ b/tests/Feature/Generators/ControllerGeneratorTest.php
@@ -46,6 +46,7 @@ class ControllerGeneratorTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider controllerTreeDataProvider
      */
     public function output_writes_migration_for_controller_tree($definition, $path, $controller)
@@ -240,4 +241,31 @@ class ControllerGeneratorTest extends TestCase
             ['drafts/invokable-controller-shorthand.yaml', 'app/Http/Controllers/ReportController.php', 'controllers/invokable-controller-shorthand.php'],
         ];
     }
+
+    public function testOutputGeneratesControllersWithTypehints(): void
+    {
+        $definition = 'drafts/controller-returns-view-typehint.yaml';
+        $path = 'app/Http/Controllers/UserController.php';
+        $controller = 'controllers/controller-returns-view-typehint.php';
+
+        $this->app['config']->set('blueprint.use_return_types', true);
+
+        $this->filesystem->expects('stub')
+            ->with('controller.class.stub')
+            ->andReturn($this->stub('controller.class.stub'));
+        $this->filesystem->expects('stub')
+            ->with('controller.method.stub')
+            ->andReturn($this->stub('controller.method.stub'));
+
+        $this->filesystem->expects('exists')
+            ->with(dirname($path))
+            ->andReturnTrue();
+        $this->filesystem->expects('put')
+            ->with($path, $this->fixture($controller));
+
+        $tokens = $this->blueprint->parse($this->fixture($definition));
+        $tree = $this->blueprint->analyze($tokens);
+
+        self::assertSame(['created' => [$path]], $this->subject->output($tree));
+    }
 }
diff --git a/tests/Feature/Generators/FactoryGeneratorTest.php b/tests/Feature/Generators/FactoryGeneratorTest.php
index f1f93ba..bb289a3 100644
--- a/tests/Feature/Generators/FactoryGeneratorTest.php
+++ b/tests/Feature/Generators/FactoryGeneratorTest.php
@@ -47,6 +47,7 @@ class FactoryGeneratorTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider modelTreeDataProvider
      */
     public function output_writes_factory_for_model_tree($definition, $path, $factory)
diff --git a/tests/Feature/Generators/MigrationGeneratorTest.php b/tests/Feature/Generators/MigrationGeneratorTest.php
index 3670655..5ca2e67 100644
--- a/tests/Feature/Generators/MigrationGeneratorTest.php
+++ b/tests/Feature/Generators/MigrationGeneratorTest.php
@@ -48,6 +48,7 @@ class MigrationGeneratorTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider modelTreeDataProvider
      */
     public function output_writes_migration_for_model_tree($definition, $path, $migration)
@@ -80,6 +81,7 @@ class MigrationGeneratorTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider modelTreeDataProvider
      */
     public function output_updates_migration_for_model_tree($definition, $path, $migration)
diff --git a/tests/Feature/Generators/ModelGeneratorTest.php b/tests/Feature/Generators/ModelGeneratorTest.php
index d3c3b73..22a2430 100644
--- a/tests/Feature/Generators/ModelGeneratorTest.php
+++ b/tests/Feature/Generators/ModelGeneratorTest.php
@@ -43,6 +43,7 @@ class ModelGeneratorTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider modelTreeDataProvider
      */
     public function output_generates_models($definition, $path, $model)
@@ -393,6 +394,7 @@ class ModelGeneratorTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider docBlockModelsDataProvider
      */
     public function output_generates_phpdoc_for_model($definition, $path, $model)
diff --git a/tests/Feature/Generators/RouteGeneratorTest.php b/tests/Feature/Generators/RouteGeneratorTest.php
index b910cae..46c6259 100644
--- a/tests/Feature/Generators/RouteGeneratorTest.php
+++ b/tests/Feature/Generators/RouteGeneratorTest.php
@@ -43,6 +43,7 @@ class RouteGeneratorTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider controllerTreeDataProvider
      */
     public function output_generates_web_routes($definition, $routes)
diff --git a/tests/Feature/Generators/Statements/NotificationGeneratorTest.php b/tests/Feature/Generators/Statements/NotificationGeneratorTest.php
index 6bfc93d..6f211a7 100644
--- a/tests/Feature/Generators/Statements/NotificationGeneratorTest.php
+++ b/tests/Feature/Generators/Statements/NotificationGeneratorTest.php
@@ -64,6 +64,7 @@ class NotificationGeneratorTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider notificationDraftProvider
      */
     public function output_writes_notifications($draft)
diff --git a/tests/Feature/Generators/TestGeneratorTest.php b/tests/Feature/Generators/TestGeneratorTest.php
index f198a15..93ec9d7 100644
--- a/tests/Feature/Generators/TestGeneratorTest.php
+++ b/tests/Feature/Generators/TestGeneratorTest.php
@@ -46,6 +46,7 @@ class TestGeneratorTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider controllerTreeDataProvider
      */
     public function output_generates_test_for_controller_tree_l8($definition, $path, $test)
diff --git a/tests/Feature/Lexers/ModelLexerTest.php b/tests/Feature/Lexers/ModelLexerTest.php
index 156b3c6..da28f48 100644
--- a/tests/Feature/Lexers/ModelLexerTest.php
+++ b/tests/Feature/Lexers/ModelLexerTest.php
@@ -269,6 +269,7 @@ class ModelLexerTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider dataTypeAttributesDataProvider
      */
     public function it_handles_data_type_attributes($definition, $data_type, $attributes)
@@ -304,6 +305,7 @@ class ModelLexerTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider modifierAttributesProvider
      */
     public function it_handles_modifier_attributes($definition, $modifier, $attributes)
diff --git a/tests/Feature/Lexers/StatementLexerTest.php b/tests/Feature/Lexers/StatementLexerTest.php
index 6a8ae11..d279b6f 100644
--- a/tests/Feature/Lexers/StatementLexerTest.php
+++ b/tests/Feature/Lexers/StatementLexerTest.php
@@ -433,6 +433,7 @@ class StatementLexerTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider eloquentTokensProvider
      */
     public function it_returns_an_eloquent_statement($operation, $reference)
@@ -471,6 +472,7 @@ class StatementLexerTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider sessionTokensProvider
      */
     public function it_returns_a_session_statement($operation, $reference)
diff --git a/tests/Feature/Translators/RulesTest.php b/tests/Feature/Translators/RulesTest.php
index 585d283..9654df5 100644
--- a/tests/Feature/Translators/RulesTest.php
+++ b/tests/Feature/Translators/RulesTest.php
@@ -33,6 +33,7 @@ class RulesTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider stringDataTypesProvider
      */
     public function forColumn_returns_string_rule_for_string_data_types($data_type)
@@ -58,6 +59,7 @@ class RulesTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider stringDataTypesProvider
      */
     public function forColumn_uses_email_rule_for_columns_named_email_or_email_address($data_type)
@@ -75,6 +77,7 @@ class RulesTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider stringDataTypesProvider
      */
     public function forColumn_uses_password_rule_for_columns_named_password($data_type)
@@ -87,6 +90,7 @@ class RulesTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider numericDataTypesProvider
      */
     public function forColumn_returns_numeric_rule_for_numeric_types($data_type)
@@ -98,6 +102,7 @@ class RulesTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider integerDataTypesProvider
      */
     public function forColumn_returns_integer_rule_for_integer_types($data_type)
@@ -138,6 +143,7 @@ class RulesTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider relationshipColumnProvider
      */
     public function forColumn_returns_exists_rule_for_foreign_keys($name, $table)
@@ -179,6 +185,7 @@ class RulesTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider dateDataTypesProvider
      */
     public function forColumn_returns_date_rule_for_date_types($data_type)
@@ -224,6 +231,7 @@ class RulesTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider noBetweenRuleDataProvider
      */
     public function forColumn_does_not_return_between_rule_for_double_without_precision_and_scale($column)
@@ -233,6 +241,7 @@ class RulesTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider noBetweenRuleDataProvider
      */
     public function forColumn_does_not_return_between_rule($column)
@@ -242,6 +251,7 @@ class RulesTest extends TestCase
 
     /**
      * @test
+     *
      * @dataProvider betweenRuleDataProvider
      */
     public function forColumn_returns_between_rule($column, $interval)
diff --git a/tests/Unit/EnumTypeTest.php b/tests/Unit/EnumTypeTest.php
index fb33b7a..35602db 100644
--- a/tests/Unit/EnumTypeTest.php
+++ b/tests/Unit/EnumTypeTest.php
@@ -11,6 +11,7 @@ class EnumTypeTest extends TestCase
 {
     /**
      * @test
+     *
      * @dataProvider enumOptionsDataProvider
      */
     public function it_returns_options_for_enum($definition, $expected)
diff --git a/tests/fixtures/controllers/controller-returns-view-typehint.php b/tests/fixtures/controllers/controller-returns-view-typehint.php
new file mode 100644
index 0000000..b64d70d
--- /dev/null
+++ b/tests/fixtures/controllers/controller-returns-view-typehint.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Requests\UserStoreRequest;
+use App\Http\Requests\UserUpdateRequest;
+use App\Models\User;
+use Illuminate\Http\Request;
+
+class UserController extends Controller
+{
+    /**
+     * @param \Illuminate\Http\Request $request
+     * @return \Illuminate\View\View
+     */
+    public function index(Request $request): \Illuminate\View\View
+    {
+        $users = User::all();
+
+        return view('user.index', compact('users'));
+    }
+
+    /**
+     * @param \Illuminate\Http\Request $request
+     * @return \Illuminate\View\View
+     */
+    public function create(Request $request): \Illuminate\View\View
+    {
+        return view('user.create');
+    }
+
+    /**
+     * @param \App\Http\Requests\UserStoreRequest $request
+     * @return \Illuminate\Routing\Redirector
+     */
+    public function store(UserStoreRequest $request): \Illuminate\Routing\Redirector
+    {
+        $user = User::create($request->validated());
+
+        $request->session()->flash('user.id', $user->id);
+
+        return redirect()->route('user.index');
+    }
+
+    /**
+     * @param \Illuminate\Http\Request $request
+     * @param \App\Models\User $user
+     * @return \Illuminate\View\View
+     */
+    public function show(Request $request, User $user): \Illuminate\View\View
+    {
+        return view('user.show', compact('user'));
+    }
+
+    /**
+     * @param \Illuminate\Http\Request $request
+     * @param \App\Models\User $user
+     * @return \Illuminate\View\View
+     */
+    public function edit(Request $request, User $user): \Illuminate\View\View
+    {
+        return view('user.edit', compact('user'));
+    }
+
+    /**
+     * @param \App\Http\Requests\UserUpdateRequest $request
+     * @param \App\Models\User $user
+     * @return \Illuminate\Routing\Redirector
+     */
+    public function update(UserUpdateRequest $request, User $user): \Illuminate\Routing\Redirector
+    {
+        $user->update($request->validated());
+
+        $request->session()->flash('user.id', $user->id);
+
+        return redirect()->route('user.index');
+    }
+
+    /**
+     * @param \Illuminate\Http\Request $request
+     * @param \App\Models\User $user
+     * @return \Illuminate\Routing\Redirector
+     */
+    public function destroy(Request $request, User $user): \Illuminate\Routing\Redirector
+    {
+        $user->delete();
+
+        return redirect()->route('user.index');
+    }
+}
diff --git a/tests/fixtures/controllers/return-type-declarations.php b/tests/fixtures/controllers/return-type-declarations.php
index c055009..9ad6ef8 100644
--- a/tests/fixtures/controllers/return-type-declarations.php
+++ b/tests/fixtures/controllers/return-type-declarations.php
@@ -11,9 +11,9 @@ class TermController extends Controller
 {
     /**
      * @param \Illuminate\Http\Request $request
-     * @return \Illuminate\Http\Response
+     * @return \Illuminate\View\View
      */
-    public function index(Request $request): \Illuminate\Http\Response
+    public function index(Request $request): \Illuminate\View\View
     {
         $terms = Term::all();
 
@@ -22,18 +22,18 @@ class TermController extends Controller
 
     /**
      * @param \Illuminate\Http\Request $request
-     * @return \Illuminate\Http\Response
+     * @return \Illuminate\View\View
      */
-    public function create(Request $request): \Illuminate\Http\Response
+    public function create(Request $request): \Illuminate\View\View
     {
         return view('term.create');
     }
 
     /**
      * @param \App\Http\Requests\TermStoreRequest $request
-     * @return \Illuminate\Http\Response
+     * @return \Illuminate\Routing\Redirector
      */
-    public function store(TermStoreRequest $request): \Illuminate\Http\Response
+    public function store(TermStoreRequest $request): \Illuminate\Routing\Redirector
     {
         $term = Term::create($request->validated());
 
@@ -45,9 +45,9 @@ class TermController extends Controller
     /**
      * @param \Illuminate\Http\Request $request
      * @param \App\Models\Term $term
-     * @return \Illuminate\Http\Response
+     * @return \Illuminate\View\View
      */
-    public function show(Request $request, Term $term): \Illuminate\Http\Response
+    public function show(Request $request, Term $term): \Illuminate\View\View
     {
         return view('term.show', compact('term'));
     }
@@ -55,9 +55,9 @@ class TermController extends Controller
     /**
      * @param \Illuminate\Http\Request $request
      * @param \App\Models\Term $term
-     * @return \Illuminate\Http\Response
+     * @return \Illuminate\View\View
      */
-    public function edit(Request $request, Term $term): \Illuminate\Http\Response
+    public function edit(Request $request, Term $term): \Illuminate\View\View
     {
         return view('term.edit', compact('term'));
     }
@@ -65,9 +65,9 @@ class TermController extends Controller
     /**
      * @param \App\Http\Requests\TermUpdateRequest $request
      * @param \App\Models\Term $term
-     * @return \Illuminate\Http\Response
+     * @return \Illuminate\Routing\Redirector
      */
-    public function update(TermUpdateRequest $request, Term $term): \Illuminate\Http\Response
+    public function update(TermUpdateRequest $request, Term $term): \Illuminate\Routing\Redirector
     {
         $term->update($request->validated());
 
@@ -79,9 +79,9 @@ class TermController extends Controller
     /**
      * @param \Illuminate\Http\Request $request
      * @param \App\Models\Term $term
-     * @return \Illuminate\Http\Response
+     * @return \Illuminate\Routing\Redirector
      */
-    public function destroy(Request $request, Term $term): \Illuminate\Http\Response
+    public function destroy(Request $request, Term $term): \Illuminate\Routing\Redirector
     {
         $term->delete();
 
diff --git a/tests/fixtures/drafts/controller-returns-view-typehint.yaml b/tests/fixtures/drafts/controller-returns-view-typehint.yaml
new file mode 100644
index 0000000..1087b8e
--- /dev/null
+++ b/tests/fixtures/drafts/controller-returns-view-typehint.yaml
@@ -0,0 +1,12 @@
+models:
+  User:
+    name: string
+    email: string unique
+    email_verified_at: datetime nullable
+    password: string
+    remember_token: rememberToken
+    softDeletes
+
+controllers:
+  User:
+    resource