Repository: Lomkit/laravel-rest-api (php)

Issue:
Error searchQuery when using laravel/spatie-permission
### Laravel Rest Api Version

1.1.1

### Laravel Version

10.24.0

### PHP Version

8.1

### Database Driver & Version

_No response_

### Description

Error: Call to a member function resource() on null in file /var/www/vendor/lomkit/laravel-rest-api/src/Http/Response.php on line 92

This error occurs when using laravel/spatie-permission. By including roles in the User model via the variable $with because the relationship with roles is defined directly with the package, so I didn't define it in the UserResource.

### Steps To Reproduce

- Add laravel/spatie-permission
- Set-up roles/permissions migrations
- Add $with in model User `protected $with = ['roles'];`
- Try to use search method `users/search`

Pull request:
Fix/auto loaded relation
closes #48 

Patch:
diff --git a/src/Http/Response.php b/src/Http/Response.php
index c342413..4cb296d 100644
--- a/src/Http/Response.php
+++ b/src/Http/Response.php
@@ -43,7 +43,7 @@ class Response implements Responsable
 
     public function modelToResponse(Model $model, Resource $resource, array $requestArray, Relation $relation = null)
     {
-        $currentRequestArray = $relation === null ? $requestArray : collect($requestArray['includes'])
+        $currentRequestArray = $relation === null ? $requestArray : collect($requestArray['includes'] ?? [])
             ->first(function ($include) use ($relation) {
                 return preg_match('/(?:\.\b)?'.$relation->relation.'\b/', $include['relation']);
             });


Test patch:
diff --git a/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php b/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php
index 8dcdce1..cc57dcc 100644
--- a/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php
+++ b/tests/Feature/Controllers/SearchIncludingRelationshipsOperationsTest.php
@@ -11,12 +11,14 @@ use Lomkit\Rest\Tests\Support\Database\Factories\HasManyRelationFactory;
 use Lomkit\Rest\Tests\Support\Database\Factories\HasOneOfManyRelationFactory;
 use Lomkit\Rest\Tests\Support\Database\Factories\HasOneRelationFactory;
 use Lomkit\Rest\Tests\Support\Database\Factories\ModelFactory;
+use Lomkit\Rest\Tests\Support\Database\Factories\ModelWithFactory;
 use Lomkit\Rest\Tests\Support\Models\BelongsToManyRelation;
 use Lomkit\Rest\Tests\Support\Models\BelongsToRelation;
 use Lomkit\Rest\Tests\Support\Models\HasManyRelation;
 use Lomkit\Rest\Tests\Support\Models\HasOneOfManyRelation;
 use Lomkit\Rest\Tests\Support\Models\HasOneRelation;
 use Lomkit\Rest\Tests\Support\Models\Model;
+use Lomkit\Rest\Tests\Support\Models\ModelWith;
 use Lomkit\Rest\Tests\Support\Policies\GreenPolicy;
 use Lomkit\Rest\Tests\Support\Rest\Resources\BelongsToManyResource;
 use Lomkit\Rest\Tests\Support\Rest\Resources\BelongsToResource;
@@ -171,6 +173,41 @@ class SearchIncludingRelationshipsOperationsTest extends TestCase
         );
     }
 
+    public function test_getting_a_list_of_resources_with_auto_loaded_relation(): void
+    {
+        $belongsTo = BelongsToRelationFactory::new()->create();
+        $matchingModel = ModelWithFactory::new()
+            ->for($belongsTo)
+            ->create()->fresh();
+
+        $matchingModel2 = ModelWithFactory::new()->create()->fresh();
+
+        Gate::policy(ModelWith::class, GreenPolicy::class);
+        Gate::policy(BelongsToRelation::class, GreenPolicy::class);
+
+        $response = $this->post(
+            '/api/model-withs/search',
+            [
+                'search' => [],
+            ],
+            ['Accept' => 'application/json']
+        );
+
+        $this->assertResourcePaginated(
+            $response,
+            [$matchingModel, $matchingModel2],
+            new ModelResource(),
+            [
+                [
+                    'belongs_to_relation' => $matchingModel->belongsToRelation->only((new BelongsToResource())->getFields(app()->make(RestRequest::class))),
+                ],
+                [
+                    'belongs_to_relation' => null,
+                ],
+            ]
+        );
+    }
+
     public function test_getting_a_list_of_resources_including_belongs_to_has_many_relation(): void
     {
         $belongsTo = BelongsToRelationFactory::new()->create();
diff --git a/tests/Support/Database/Factories/ModelWithFactory.php b/tests/Support/Database/Factories/ModelWithFactory.php
new file mode 100644
index 0000000..41c2e1c
--- /dev/null
+++ b/tests/Support/Database/Factories/ModelWithFactory.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Lomkit\Rest\Tests\Support\Database\Factories;
+
+use Illuminate\Database\Eloquent\Factories\Factory;
+use Lomkit\Rest\Tests\Support\Models\Model;
+use Lomkit\Rest\Tests\Support\Models\ModelWith;
+
+class ModelWithFactory extends Factory
+{
+    /**
+     * The name of the factory's corresponding model.
+     *
+     * @var class-string<\Illuminate\Database\Eloquent\Model|TModel>
+     */
+    protected $model = ModelWith::class;
+
+    /**
+     * Define the model's default state.
+     *
+     * @return array
+     */
+    public function definition()
+    {
+        return [
+            'name'   => fake()->name(),
+            'number' => fake()->numberBetween(-9999999, 9999999),
+        ];
+    }
+}
diff --git a/tests/Support/Database/migrations/2023_04_00_000000_create_model_withs_table.php b/tests/Support/Database/migrations/2023_04_00_000000_create_model_withs_table.php
new file mode 100644
index 0000000..02bcd63
--- /dev/null
+++ b/tests/Support/Database/migrations/2023_04_00_000000_create_model_withs_table.php
@@ -0,0 +1,24 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class() extends Migration {
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('model_withs', function (Blueprint $table) {
+            $table->id();
+            $table->string('name');
+            $table->bigInteger('number');
+            $table->string('string')->nullable();
+            $table->foreignIdFor(\Lomkit\Rest\Tests\Support\Models\BelongsToRelation::class)->nullable()->constrained();
+            $table->timestamps();
+        });
+    }
+};
diff --git a/tests/Support/Http/Controllers/ModelWithController.php b/tests/Support/Http/Controllers/ModelWithController.php
new file mode 100644
index 0000000..a99dcad
--- /dev/null
+++ b/tests/Support/Http/Controllers/ModelWithController.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Lomkit\Rest\Tests\Support\Http\Controllers;
+
+use Lomkit\Rest\Http\Controllers\Controller;
+use Lomkit\Rest\Tests\Support\Rest\Resources\ModelWithResource;
+
+class ModelWithController extends Controller
+{
+    public static $resource = ModelWithResource::class;
+}
diff --git a/tests/Support/Models/ModelWith.php b/tests/Support/Models/ModelWith.php
new file mode 100644
index 0000000..8d92479
--- /dev/null
+++ b/tests/Support/Models/ModelWith.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Lomkit\Rest\Tests\Support\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model as BaseModel;
+use Lomkit\Rest\Tests\Support\Database\Factories\ModelFactory;
+
+class ModelWith extends BaseModel
+{
+    use HasFactory;
+
+    protected static function newFactory()
+    {
+        return new ModelFactory();
+    }
+
+    protected $with = [
+        'belongsToRelation',
+    ];
+
+    protected $fillable = [
+        'id',
+    ];
+
+    public function belongsToRelation()
+    {
+        return $this->belongsTo(BelongsToRelation::class);
+    }
+}
diff --git a/tests/Support/Rest/Resources/ModelWithResource.php b/tests/Support/Rest/Resources/ModelWithResource.php
new file mode 100644
index 0000000..8d161bc
--- /dev/null
+++ b/tests/Support/Rest/Resources/ModelWithResource.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Lomkit\Rest\Tests\Support\Rest\Resources;
+
+use Lomkit\Rest\Http\Requests\RestRequest;
+use Lomkit\Rest\Http\Resource;
+use Lomkit\Rest\Relations\BelongsTo;
+use Lomkit\Rest\Tests\Support\Models\ModelWith;
+
+class ModelWithResource extends Resource
+{
+    public static $model = ModelWith::class;
+
+    public function fields(RestRequest $request): array
+    {
+        return [
+            'id',
+            'name',
+            'number',
+            'string',
+        ];
+    }
+
+    public function defaultOrderBy(RestRequest $request): array
+    {
+        return [
+            'id' => 'asc',
+        ];
+    }
+
+    public function relations(RestRequest $request): array
+    {
+        return [
+            BelongsTo::make('belongsToRelation', BelongsToResource::class),
+        ];
+    }
+}
diff --git a/tests/Support/Routes/api.php b/tests/Support/Routes/api.php
index c5d1d18..621245b 100644
--- a/tests/Support/Routes/api.php
+++ b/tests/Support/Routes/api.php
@@ -1,10 +1,10 @@
 <?php
 
 use Illuminate\Support\Facades\Route;
-use Lomkit\Rest\Tests\Support\Http\Controllers\ModelController;
 
 Route::group(['as' => 'api.', 'prefix' => 'api'], function () {
-    \Lomkit\Rest\Facades\Rest::resource('models', ModelController::class);
+    \Lomkit\Rest\Facades\Rest::resource('models', \Lomkit\Rest\Tests\Support\Http\Controllers\ModelController::class);
+    \Lomkit\Rest\Facades\Rest::resource('model-withs', \Lomkit\Rest\Tests\Support\Http\Controllers\ModelWithController::class);
 
     \Lomkit\Rest\Facades\Rest::resource('no-exposed-fields', \Lomkit\Rest\Tests\Support\Http\Controllers\NoExposedFieldsController::class);
     \Lomkit\Rest\Facades\Rest::resource('automatic-gating', \Lomkit\Rest\Tests\Support\Http\Controllers\AutomaticGatingController::class);