Repository: laravel-shift/blueprint (php)

Issue:
Error using pagination in index method
<!-- PLEASE COMPLETE THIS TEMPLATE -->

- Laravel Version: 8.31.0
- PHP Version: 7.4.3
- Blueprint Version: 1.22.0
- Platform: Linux

### Issue: Error using pagination in index method.
Hello.
When I define a controller specifying the resource type as `resource: api` and in the `index` method it is specified as `query: all` and `resource: 'paginate:posts'`, as shown below:
```yaml
models:
  Post:
    title: string
    body: text
controllers:
  Post:
    resource: api
    index:
      query: all
      resource: 'paginate:posts'
seeders: Post
```

The above code generates the following files:
![imagen](https://user-images.githubusercontent.com/4079493/110160013-7036e680-7db9-11eb-9c99-f17b32f93e2d.png)

The problem comes in the index method of the controller:
```php
public function index(Request $request)
{
    $posts = Post::all();

    return new PostCollection($posts->paginate());
}
```
First it generates a `$posts` variable and assigns it the results of `Post::all()` and then tries to call the `paginate()` method on the `$post` variable. This leads to an error when I make a request to the api/post route:
![imagen](https://user-images.githubusercontent.com/4079493/110160983-ba6c9780-7dba-11eb-809e-4e54818ed1bb.png)
```json
{
    "exception": "BadMethodCallException",
    "file": "/home/cristian/Proyectos/blueprint-test/vendor/laravel/framework/src/Illuminate/Macroable/Traits/Macroable.php",
    "line": 103,
    "message": "Method Illuminate\\Database\\Eloquent\\Collection::paginate does not exist.",
    "trace": [
        {
            "class": "Illuminate\\Support\\Collection",
            "file": "/home/cristian/Proyectos/blueprint-test/app/Http/Controllers/PostController.php",
            "function": "__call",
            "line": 22,
            "type": "->"
        }
     ]
}
```

The correct code that should be generated is:
```php
public function index(Request $request)
{
    $posts = Post::paginate();
    
    return new PostCollection($posts);
}
```

### draft.yaml:
```yaml
models:
  Post:
    title: string
    body: text
controllers:
  Post:
    resource: api
    index:
      query: all
      resource: 'paginate:posts'
seeders: Post

```


Pull request:
Fix resource pagination
- resolves #439
- `QueryStatement` can now determine the Model from `get` and `pluck` operations.
- No longer a need to define `query: all` when you define `resource: 'paginate:{model}'`.

```diff
# eg. draft.yaml
models:
  Post:
    title: string
    body: text
controllers:
  Post:
    resource: api
    index:
-      query: all
      resource: 'paginate:posts'
seeders: Post
```

Patch:
diff --git a/src/Generators/ControllerGenerator.php b/src/Generators/ControllerGenerator.php
index a9985a0..8126497 100644
--- a/src/Generators/ControllerGenerator.php
+++ b/src/Generators/ControllerGenerator.php
@@ -142,6 +142,20 @@ class ControllerGenerator implements Generator
                     $method = str_replace('* @return \\Illuminate\\Http\\Response', '* @return \\' . $fqcn, $method);
                     $this->addImport($controller, $fqcn);
                     $body .= self::INDENT . $statement->output() . PHP_EOL;
+
+                    if ($statement->paginate()) {
+                        if (! Str::contains($body, '::all();')) {
+                            $queryStatement = new QueryStatement('all', [$statement->reference()]);
+                            $body = implode(PHP_EOL, [
+                                self::INDENT . $queryStatement->output($statement->reference()),
+                                PHP_EOL . $body
+                            ]);
+
+                            $this->addImport($controller, $this->determineModel($controller, $queryStatement->model()));
+                        }
+
+                        $body = str_replace('::all();', '::paginate();', $body);
+                    }
                 } elseif ($statement instanceof RedirectStatement) {
                     $body .= self::INDENT . $statement->output() . PHP_EOL;
                 } elseif ($statement instanceof RespondStatement) {
diff --git a/src/Models/Statements/QueryStatement.php b/src/Models/Statements/QueryStatement.php
index 2126ec1..b4409dd 100644
--- a/src/Models/Statements/QueryStatement.php
+++ b/src/Models/Statements/QueryStatement.php
@@ -26,9 +26,7 @@ class QueryStatement
         $this->operation = $operation;
         $this->clauses = $clauses;
 
-        if ($operation === 'all' && !empty($clauses)) {
-            $this->model = Str::studly(Str::singular($clauses[0]));
-        }
+        $this->determineModel($this->model);
     }
 
     public function operation(): string
@@ -51,11 +49,11 @@ class QueryStatement
         $model = $this->determineModel($controller);
 
         if ($this->operation() === 'all') {
-            if (is_null($this->model())) {
-                return '$' . Str::camel(Str::plural($model)) . ' = ' . $model . '::all();';
-            } else {
-                return '$' . Str::camel($this->clauses()[0]) . ' = ' . $this->model() . '::all();';
-            }
+            return '$' . Str::camel(Str::plural($model)) . ' = ' . $model . '::all();';
+        }
+
+        if ($this->operation() === 'paginate') {
+            return '$' . Str::camel(Str::plural($model)) . ' = ' . $model . '::paginate();';
         }
 
         $methods = [];
@@ -117,15 +115,23 @@ class QueryStatement
             return Str::lower(Str::plural(str_replace('.', '_', $field)));
         }
 
-        return Str::lower('Post' . '_' . Str::plural($field));
+        return Str::lower($this->model . '_' . Str::plural($field));
     }
 
-    private function determineModel(string $prefix)
+    private function determineModel(?string $controller)
     {
-        if (empty($this->model())) {
-            return Str::studly(Str::singular($prefix));
+        if (! is_null($controller) && ! empty($controller)) {
+            $this->model = Str::studly(Str::singular($controller));
+        }
+
+        if (
+            is_null($this->model()) &&
+            ! empty($this->clauses()) &&
+            ! in_array($this->operation(), ['count','exists'])
+        ) {
+            $this->model = Str::studly(Str::singular(Str::before(Str::after($this->clauses()[0], ':'), '.')));
         }
 
-        return Str::studly($this->model());
+        return $this->model();
     }
 }
diff --git a/src/Models/Statements/ResourceStatement.php b/src/Models/Statements/ResourceStatement.php
index 1bd7873..8bb8df1 100644
--- a/src/Models/Statements/ResourceStatement.php
+++ b/src/Models/Statements/ResourceStatement.php
@@ -54,15 +54,6 @@ class ResourceStatement
 
     public function output(): string
     {
-        $code = 'return new ' . $this->name();
-        $code .= '($' . $this->reference();
-
-        if ($this->paginate()) {
-            $code .= '->paginate()';
-        }
-
-        $code .= ');';
-
-        return $code;
+        return sprintf('return new %s($%s);', $this->name(), $this->reference());
     }
 }


Test patch:
diff --git a/tests/Feature/Generators/ControllerGeneratorTest.php b/tests/Feature/Generators/ControllerGeneratorTest.php
index 1d78136..663f670 100644
--- a/tests/Feature/Generators/ControllerGeneratorTest.php
+++ b/tests/Feature/Generators/ControllerGeneratorTest.php
@@ -208,6 +208,7 @@ class ControllerGeneratorTest extends TestCase
             ['drafts/respond-statements.yaml', 'app/Http/Controllers/Api/PostController.php', 'controllers/respond-statements.php'],
             ['drafts/resource-statements.yaml', 'app/Http/Controllers/UserController.php', 'controllers/resource-statements.php'],
             ['drafts/save-without-validation.yaml', 'app/Http/Controllers/PostController.php', 'controllers/save-without-validation.php'],
+            ['drafts/api-resource-pagination.yaml', 'app/Http/Controllers/PostController.php', 'controllers/api-resource-pagination.php'],
             ['drafts/api-routes-example.yaml', 'app/Http/Controllers/Api/CertificateController.php', 'controllers/api-routes-example.php'],
             ['drafts/invokable-controller.yaml', 'app/Http/Controllers/ReportController.php', 'controllers/invokable-controller.php'],
             ['drafts/invokable-controller-shorthand.yaml', 'app/Http/Controllers/ReportController.php', 'controllers/invokable-controller-shorthand.php'],
diff --git a/tests/Feature/Generators/Statements/ResourceGeneratorTest.php b/tests/Feature/Generators/Statements/ResourceGeneratorTest.php
index 4651028..7f74f72 100644
--- a/tests/Feature/Generators/Statements/ResourceGeneratorTest.php
+++ b/tests/Feature/Generators/Statements/ResourceGeneratorTest.php
@@ -136,4 +136,40 @@ class ResourceGeneratorTest extends TestCase
             'created' => ['app/Http/Resources/Api/CertificateCollection.php', 'app/Http/Resources/Api/CertificateResource.php'],
         ], $this->subject->output($tree));
     }
+
+    /**
+     * @test
+     */
+    public function output_api_resource_pagination()
+    {
+        $this->files->expects('stub')
+            ->with('resource.stub')
+            ->andReturn(file_get_contents('stubs/resource.stub'));
+
+        $this->files->shouldReceive('exists')
+            ->with('app/Http/Resources')
+            ->andReturns(false, true);
+        $this->files->expects('makeDirectory')
+            ->with('app/Http/Resources', 0755, true);
+
+        $this->files->expects('exists')
+            ->times(3)
+            ->with('app/Http/Resources/PostResource.php')
+            ->andReturns(false, true, true);
+        $this->files->expects('put')
+            ->with('app/Http/Resources/PostResource.php', $this->fixture('resources/api-post-resource.php'));
+
+        $this->files->expects('exists')
+            ->with('app/Http/Resources/PostCollection.php')
+            ->andReturns(false);
+        $this->files->expects('put')
+            ->with('app/Http/Resources/PostCollection.php', $this->fixture('resources/api-resource-pagination.php'));
+
+        $tokens = $this->blueprint->parse($this->fixture('drafts/api-resource-pagination.yaml'));
+        $tree = $this->blueprint->analyze($tokens);
+
+        $this->assertEquals([
+            'created' => ['app/Http/Resources/PostCollection.php', 'app/Http/Resources/PostResource.php'],
+        ], $this->subject->output($tree));
+    }
 }
diff --git a/tests/Feature/Lexers/StatementLexerTest.php b/tests/Feature/Lexers/StatementLexerTest.php
index 030eecf..bd60e7c 100644
--- a/tests/Feature/Lexers/StatementLexerTest.php
+++ b/tests/Feature/Lexers/StatementLexerTest.php
@@ -589,7 +589,7 @@ class StatementLexerTest extends TestCase
 
         $this->assertEquals('get', $actual[0]->operation());
         $this->assertSame(['where:post.title', 'order:post.created_at'], $actual[0]->clauses());
-        $this->assertNull($actual[0]->model());
+        $this->assertSame('Post', $actual[0]->model());
     }
 
     /**
@@ -608,7 +608,7 @@ class StatementLexerTest extends TestCase
 
         $this->assertEquals('pluck', $actual[0]->operation());
         $this->assertSame(['order:post.created_at', 'pluck:id'], $actual[0]->clauses());
-        $this->assertNull($actual[0]->model());
+        $this->assertSame('Post', $actual[0]->model());
     }
 
     /**
diff --git a/tests/fixtures/controllers/api-resource-pagination.php b/tests/fixtures/controllers/api-resource-pagination.php
new file mode 100644
index 0000000..98bd063
--- /dev/null
+++ b/tests/fixtures/controllers/api-resource-pagination.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Requests\PostStoreRequest;
+use App\Http\Requests\PostUpdateRequest;
+use App\Http\Resources\PostCollection;
+use App\Http\Resources\PostResource;
+use App\Post;
+use Illuminate\Http\Request;
+
+class PostController extends Controller
+{
+    /**
+     * @param \Illuminate\Http\Request $request
+     * @return \App\Http\Resources\PostCollection
+     */
+    public function index(Request $request)
+    {
+        $posts = Post::paginate();
+
+        return new PostCollection($posts);
+    }
+
+    /**
+     * @param \App\Http\Requests\PostStoreRequest $request
+     * @return \App\Http\Resources\PostResource
+     */
+    public function store(PostStoreRequest $request)
+    {
+        $post = Post::create($request->validated());
+
+        return new PostResource($post);
+    }
+
+    /**
+     * @param \Illuminate\Http\Request $request
+     * @param \App\Post $post
+     * @return \App\Http\Resources\PostResource
+     */
+    public function show(Request $request, Post $post)
+    {
+        return new PostResource($post);
+    }
+
+    /**
+     * @param \App\Http\Requests\PostUpdateRequest $request
+     * @param \App\Post $post
+     * @return \App\Http\Resources\PostResource
+     */
+    public function update(PostUpdateRequest $request, Post $post)
+    {
+        $post->update($request->validated());
+
+        return new PostResource($post);
+    }
+
+    /**
+     * @param \Illuminate\Http\Request $request
+     * @param \App\Post $post
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request, Post $post)
+    {
+        $post->delete();
+
+        return response()->noContent();
+    }
+}
diff --git a/tests/fixtures/controllers/resource-statements.php b/tests/fixtures/controllers/resource-statements.php
index d620bc0..5ac4f1b 100644
--- a/tests/fixtures/controllers/resource-statements.php
+++ b/tests/fixtures/controllers/resource-statements.php
@@ -15,7 +15,9 @@ class UserController extends Controller
      */
     public function index(Request $request)
     {
-        return new UserCollection($users->paginate());
+        $users = User::paginate();
+
+        return new UserCollection($users);
     }
 
     /**
diff --git a/tests/fixtures/drafts/api-resource-pagination.yaml b/tests/fixtures/drafts/api-resource-pagination.yaml
new file mode 100644
index 0000000..c8c2cb1
--- /dev/null
+++ b/tests/fixtures/drafts/api-resource-pagination.yaml
@@ -0,0 +1,11 @@
+models:
+  Post:
+    title: string
+    body: text
+controllers:
+  Post:
+    resource: api
+    index:
+      query: all
+      resource: 'paginate:posts'
+seeders: Post
diff --git a/tests/fixtures/resources/api-post-resource.php b/tests/fixtures/resources/api-post-resource.php
new file mode 100644
index 0000000..5b09275
--- /dev/null
+++ b/tests/fixtures/resources/api-post-resource.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class PostResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array
+     */
+    public function toArray($request)
+    {
+        return [
+            'id' => $this->id,
+            'title' => $this->title,
+            'body' => $this->body,
+        ];
+    }
+}
diff --git a/tests/fixtures/resources/api-resource-pagination.php b/tests/fixtures/resources/api-resource-pagination.php
new file mode 100644
index 0000000..18301fc
--- /dev/null
+++ b/tests/fixtures/resources/api-resource-pagination.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\ResourceCollection;
+
+class PostCollection extends ResourceCollection
+{
+    /**
+     * Transform the resource collection into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array
+     */
+    public function toArray($request)
+    {
+        return [
+            'data' => $this->collection,
+        ];
+    }
+}