Repository: laravel/framework (php)

Issue:
[9.12.0] Enum bug within relationship
<!-- DO NOT THROW THIS AWAY -->
<!-- Fill out the FULL versions with patch versions -->

- Laravel Version: 9.12.0
- PHP Version: 8.1.5
- Database Driver & Version: MariaDB 

### Description:
I suddenly get these errors after this commit (laravel v9.12.0), commit #42100.
v9.11.0 and earlier works correct...

```
str_starts_with(): Argument #1 ($haystack) must be of type string, App\Models\Enums\ProductType given
```

Seems to be caused by this line, https://github.com/laravel/framework/blob/9.x/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php#L786=


### Steps To Reproduce:
I have a product model, with a product_type with an enum. The problem appears when doing something like this

--
This is located in my Product model

```
/**
     * Get all sub products
     *
     * @return \Illuminate\Database\Eloquent\Relations\hasMany
     */
    public function simpleProducts()
    {
        return $this->hasMany(self::class, 'parent_id', 'id')
            ->where('active', 1)
            ->where('product_type', ProductType::SIMPLE)
            ->orderBy('sort');
    }
```

ProductType enum
```
namespace App\Models\Enums;

enum ProductType: string
{
    case SIMPLE = 'simple';
    case GROUPED = 'grouped';
    case CONFIGURABLE = 'configurable';
}
```

```
return Product::withCount('simpleProducts')
    ->get();
```

Calling this works correct....

```
return Product::where('product_type', ProductType::SIMPLE)
    ->get();
```


Pull request:
[9.x] Add `is_string` check to `QueriesRelationships@requalifyWhereTables`
<!--
Please only send a pull request to branches that are currently supported: https://laravel.com/docs/releases#support-policy 

If you are unsure which branch your pull request should be sent to, please read: https://laravel.com/docs/contributions#which-branch

Pull requests without a descriptive title, thorough description, or tests will be closed.

In addition, please describe the benefit to end users; the reasons it does not break any existing features; how it makes building web applications easier, etc.
-->


Closes #42338

This PR:

- Adds a `is_string()` check before a `str_starts_with()` on `QueriesRelationships@requalifyWhereTables`
- Adds a test case which would fail without this patch

Notes:

- The `QueriesRelationships@requalifyWhereTables` was added by PR #42100 which tried to fix table prefixing issues when using self-referencing relations
- There is no need to check non-string values, as the idea is to prefix values used in `whereColumn`, or other that could reference related tables
- The added test case used a `new stdClass` as enums are not available in PHP 8. As it tests the generated SQL statement, the `stdClass` instance never acutually gets bound to PDO.


EDIT: added information about added test case. Please read 3rd note about it.

Patch:
diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php
index 71979ed6cf..659969102d 100644
--- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php
+++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php
@@ -783,7 +783,7 @@ protected function requalifyWhereTables(array $wheres, string $from, string $to)
     {
         return collect($wheres)->map(function ($where) use ($from, $to) {
             return collect($where)->map(function ($value) use ($from, $to) {
-                return str_starts_with($value, $from.'.')
+                return is_string($value) && str_starts_with($value, $from.'.')
                     ? $to.'.'.Str::afterLast($value, '.')
                     : $value;
             });


Test patch:
diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php
index a1002a7d6f..6296cedcd2 100755
--- a/tests/Database/DatabaseEloquentBuilderTest.php
+++ b/tests/Database/DatabaseEloquentBuilderTest.php
@@ -1292,6 +1292,21 @@ public function testWithCountMultipleAndPartialRename()
         $this->assertSame('select "eloquent_builder_test_model_parent_stubs".*, (select count(*) from "eloquent_builder_test_model_close_related_stubs" where "eloquent_builder_test_model_parent_stubs"."foo_id" = "eloquent_builder_test_model_close_related_stubs"."id") as "foo_bar", (select count(*) from "eloquent_builder_test_model_close_related_stubs" where "eloquent_builder_test_model_parent_stubs"."foo_id" = "eloquent_builder_test_model_close_related_stubs"."id") as "foo_count" from "eloquent_builder_test_model_parent_stubs"', $builder->toSql());
     }
 
+    public function testWithAggregateAndSelfRelationConstrain()
+    {
+        EloquentBuilderTestStub::resolveRelationUsing('children', function ($model) {
+            return $model->hasMany(EloquentBuilderTestStub::class, 'parent_id', 'id')->where('enum_value', new stdClass);
+        });
+
+        $model = new EloquentBuilderTestStub;
+        $this->mockConnectionForModel($model, '');
+        $relationHash = $model->children()->getRelationCountHash(false);
+
+        $builder = $model->withCount('children');
+
+        $this->assertSame(vsprintf('select "table".*, (select count(*) from "table" as "%s" where "table"."id" = "%s"."parent_id" and "enum_value" = ?) as "children_count" from "table"', [$relationHash, $relationHash]), $builder->toSql());
+    }
+
     public function testWithExists()
     {
         $model = new EloquentBuilderTestModelParentStub;