Repository: laravel-shift/blueprint (language: php)

Issue:
Duplicated return type declarations
<!-- PLEASE COMPLETE THIS TEMPLATE -->

- Laravel Version: 8.28.1
- PHP Version: 8
- Blueprint Version: dev-master as 1.21.0
- Platform: Mac

### Issue:
- Related to PR #404 
- **Model** problem: For each relation the return type declaration is duplicated.
- **Controller** problem: Return type declaration **only** generated for `index`, `create`, `store`, the other resource methods doesn't have a return type.


### draft.yaml:
```yaml
models:
  Term:
    uuid: string unique
    name: json
    description: json nullable
    meta: json nullable
    user_id: id nullable foreign onDelete:null onUpdate:cascade
    team_id: id foreign onDelete:cascade onUpdate:cascade
    published: boolean default:1
    type: string nullable default:booking
    softDeletes
    relationships:
      belongsToMany: Organizer, Event

controllers:
  Term:
    resource
```

Models/Term.php, duplicated return type declarations
```php
//...

/**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function organizers(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
    {
        return $this->belongsToMany(\App\Models\Organizer::class);
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function events(): \Illuminate\Database\Eloquent\Relations\BelongsToMany: \Illuminate\Database\Eloquent\Relations\BelongsToMany
    {
        return $this->belongsToMany(\App\Models\Event::class);
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
    {
        return $this->belongsTo(\App\Models\User::class);
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function team(): \Illuminate\Database\Eloquent\Relations\BelongsTo: \Illuminate\Database\Eloquent\Relations\BelongsTo
    {
        return $this->belongsTo(\App\Models\Team::class);
    }

```


TermController.php, missing return type declarations
```php
<?php
//example index() has return type declaration
    /**
     * @param \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request): \Illuminate\Http\Response
    {
        $terms = Term::all();

        return view('term.index', compact('terms'));
    }

   // create() ok

   // store() ok

   // show() missing statement
    /**
     * @param \Illuminate\Http\Request $request
     * @param \App\Models\Term $term
     * @return \Illuminate\Http\Response
     */
    public function show(Request $request, Term $term)
    {
        return view('term.show', compact('term'));
    }

  // edit() missing declaration

  // update() missing declaration

  // destroy() missing declaration
```



Discussion / hints:
@nathane, would appreciate your input on this.
I’ll take a look 👀 

Fix patch:
diff --git a/src/Generators/ControllerGenerator.php b/src/Generators/ControllerGenerator.php
index 84df5846..4c7710d4 100644
--- a/src/Generators/ControllerGenerator.php
+++ b/src/Generators/ControllerGenerator.php
@@ -164,7 +164,7 @@ protected function buildMethods(Controller $controller)
             }
 
             if (Blueprint::supportsReturnTypeHits()) {
-                $method = str_replace('request)', 'request): \Illuminate\Http\Response', $method);
+                $method = str_replace(')'.PHP_EOL, '): \Illuminate\Http\Response'.PHP_EOL, $method);
             }
 
             $methods .= PHP_EOL.$method;
diff --git a/src/Generators/ModelGenerator.php b/src/Generators/ModelGenerator.php
index 01925e33..4860c7f4 100644
--- a/src/Generators/ModelGenerator.php
+++ b/src/Generators/ModelGenerator.php
@@ -184,8 +184,8 @@ protected function buildRelationships(Model $model)
         }
 
         foreach ($model->relationships() as $type => $references) {
-            $custom_template = $template;
             foreach ($references as $reference) {
+                $custom_template = $template;
                 $key = null;
                 $class = null;
 
@@ -230,6 +230,7 @@ protected function buildRelationships(Model $model)
                 } elseif (in_array($type, ['hasMany', 'belongsToMany', 'morphMany'])) {
                     $method_name = Str::plural($column_name);
                 }
+
                 if (Blueprint::supportsReturnTypeHits()) {
                     $custom_template = str_replace(
                         '{{ method }}()',


Test patch:
diff --git a/tests/Feature/Generators/ControllerGeneratorTest.php b/tests/Feature/Generators/ControllerGeneratorTest.php
index 6607b12d..7087a04d 100644
--- a/tests/Feature/Generators/ControllerGeneratorTest.php
+++ b/tests/Feature/Generators/ControllerGeneratorTest.php
@@ -189,12 +189,12 @@ public function output_using_return_types()
             ->with('app/Http/Controllers', 0755, true);
 
         $this->files->expects('put')
-            ->with('app/Http/Controllers/PostController.php', $this->fixture('controllers/return-type-declarations.php'));
+            ->with('app/Http/Controllers/TermController.php', $this->fixture('controllers/return-type-declarations.php'));
 
-        $tokens = $this->blueprint->parse($this->fixture('drafts/readme-example.yaml'));
+        $tokens = $this->blueprint->parse($this->fixture('drafts/return-type-declarations.yaml'));
         $tree = $this->blueprint->analyze($tokens);
 
-        $this->assertEquals(['created' => ['app/Http/Controllers/PostController.php']], $this->subject->output($tree));
+        $this->assertEquals(['created' => ['app/Http/Controllers/TermController.php']], $this->subject->output($tree));
     }
 
     public function controllerTreeDataProvider()
diff --git a/tests/Feature/Generators/ModelGeneratorTest.php b/tests/Feature/Generators/ModelGeneratorTest.php
index 04107bda..a0c39b99 100644
--- a/tests/Feature/Generators/ModelGeneratorTest.php
+++ b/tests/Feature/Generators/ModelGeneratorTest.php
@@ -692,7 +692,7 @@ public function laravel8ModelTreeDataProvider()
             ['drafts/resource-statements.yaml', 'app/User.php', 'models/resource-statements-laravel8.php'],
             ['drafts/all-column-types.yaml', 'app/AllType.php', 'models/all-column-types-laravel8.php'],
             ['drafts/alias-relationships.yaml', 'app/Salesman.php', 'models/alias-relationships-laravel8.php'],
-            ['drafts/alias-relationships.yaml', 'app/Salesman.php', 'models/return-type-declarations.php'],
+            ['drafts/return-type-declarations.yaml', 'app/Term.php', 'models/return-type-declarations.php'],
             ['drafts/uuid-shorthand-invalid-relationship.yaml', 'app/AgeCohort.php', 'models/uuid-shorthand-invalid-relationship-laravel8.php'],
         ];
     }
diff --git a/tests/fixtures/controllers/return-type-declarations.php b/tests/fixtures/controllers/return-type-declarations.php
index 6ff3075a..ae69f19e 100644
--- a/tests/fixtures/controllers/return-type-declarations.php
+++ b/tests/fixtures/controllers/return-type-declarations.php
@@ -2,15 +2,12 @@
 
 namespace App\Http\Controllers;
 
-use App\Events\NewPost;
-use App\Http\Requests\PostStoreRequest;
-use App\Jobs\SyncMedia;
-use App\Mail\ReviewPost;
-use App\Post;
+use App\Http\Requests\TermStoreRequest;
+use App\Http\Requests\TermUpdateRequest;
+use App\Term;
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Mail;
 
-class PostController extends Controller
+class TermController extends Controller
 {
     /**
      * @param \Illuminate\Http\Request $request
@@ -18,27 +15,76 @@ class PostController extends Controller
      */
     public function index(Request $request): \Illuminate\Http\Response
     {
-        $posts = Post::all();
+        $terms = Term::all();
 
-        return view('post.index', compact('posts'));
+        return view('term.index', compact('terms'));
     }
 
     /**
-     * @param \App\Http\Requests\PostStoreRequest $request
+     * @param \Illuminate\Http\Request $request
+     * @return \Illuminate\Http\Response
+     */
+    public function create(Request $request): \Illuminate\Http\Response
+    {
+        return view('term.create');
+    }
+
+    /**
+     * @param \App\Http\Requests\TermStoreRequest $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(TermStoreRequest $request): \Illuminate\Http\Response
+    {
+        $term = Term::create($request->validated());
+
+        $request->session()->flash('term.id', $term->id);
+
+        return redirect()->route('term.index');
+    }
+
+    /**
+     * @param \Illuminate\Http\Request $request
+     * @param \App\Term $term
+     * @return \Illuminate\Http\Response
+     */
+    public function show(Request $request, Term $term): \Illuminate\Http\Response
+    {
+        return view('term.show', compact('term'));
+    }
+
+    /**
+     * @param \Illuminate\Http\Request $request
+     * @param \App\Term $term
      * @return \Illuminate\Http\Response
      */
-    public function store(PostStoreRequest $request): \Illuminate\Http\Response
+    public function edit(Request $request, Term $term): \Illuminate\Http\Response
     {
-        $post = Post::create($request->validated());
+        return view('term.edit', compact('term'));
+    }
 
-        Mail::to($post->author->email)->send(new ReviewPost($post));
+    /**
+     * @param \App\Http\Requests\TermUpdateRequest $request
+     * @param \App\Term $term
+     * @return \Illuminate\Http\Response
+     */
+    public function update(TermUpdateRequest $request, Term $term): \Illuminate\Http\Response
+    {
+        $term->update($request->validated());
 
-        SyncMedia::dispatch($post);
+        $request->session()->flash('term.id', $term->id);
 
-        event(new NewPost($post));
+        return redirect()->route('term.index');
+    }
 
-        $request->session()->flash('post.title', $post->title);
+    /**
+     * @param \Illuminate\Http\Request $request
+     * @param \App\Term $term
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request, Term $term): \Illuminate\Http\Response
+    {
+        $term->delete();
 
-        return redirect()->route('post.index');
+        return redirect()->route('term.index');
     }
 }
diff --git a/tests/fixtures/drafts/return-type-declarations.yaml b/tests/fixtures/drafts/return-type-declarations.yaml
new file mode 100644
index 00000000..3996e6ca
--- /dev/null
+++ b/tests/fixtures/drafts/return-type-declarations.yaml
@@ -0,0 +1,17 @@
+models:
+  Term:
+    uuid: string unique
+    name: json
+    description: json nullable
+    meta: json nullable
+    user_id: id nullable foreign onDelete:null onUpdate:cascade
+    team_id: id foreign onDelete:cascade onUpdate:cascade
+    published: boolean default:1
+    type: string nullable default:booking
+    softDeletes
+    relationships:
+      belongsToMany: Organizer, Event
+
+controllers:
+  Term:
+    resource
\ No newline at end of file
diff --git a/tests/fixtures/models/return-type-declarations.php b/tests/fixtures/models/return-type-declarations.php
index 9badb0c0..6cdda52c 100644
--- a/tests/fixtures/models/return-type-declarations.php
+++ b/tests/fixtures/models/return-type-declarations.php
@@ -4,10 +4,11 @@
 
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
 
-class Salesman extends Model
+class Term extends Model
 {
-    use HasFactory;
+    use HasFactory, SoftDeletes;
 
     /**
      * The attributes that are mass assignable.
@@ -15,7 +16,14 @@ class Salesman extends Model
      * @var array
      */
     protected $fillable = [
+        'uuid',
         'name',
+        'description',
+        'meta',
+        'user_id',
+        'team_id',
+        'published',
+        'type',
     ];
 
     /**
@@ -25,21 +33,32 @@ class Salesman extends Model
      */
     protected $casts = [
         'id' => 'integer',
+        'name' => 'array',
+        'description' => 'array',
+        'meta' => 'array',
+        'user_id' => 'integer',
+        'team_id' => 'integer',
+        'published' => 'boolean',
     ];
 
 
-    public function lead(): \Illuminate\Database\Eloquent\Relations\HasOne
+    public function organizers(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
     {
-        return $this->hasOne(\App\User::class);
+        return $this->belongsToMany(\App\Organizer::class);
     }
 
-    public function methodNames(): \Illuminate\Database\Eloquent\Relations\HasMany
+    public function events(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
     {
-        return $this->hasMany(\App\ClassName::class);
+        return $this->belongsToMany(\App\Event::class);
     }
 
-    public function methodName(): \Illuminate\Database\Eloquent\Relations\BelongsTo
+    public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
     {
-        return $this->belongsTo(\App\ClassName::class);
+        return $this->belongsTo(\App\User::class);
+    }
+
+    public function team(): \Illuminate\Database\Eloquent\Relations\BelongsTo
+    {
+        return $this->belongsTo(\App\Team::class);
     }
 }